Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / builds / ext-all-sandbox-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 (function(Ext){
16 if (typeof Ext === 'undefined') {
17 this.Ext = {};
18 }
19
20 Ext.buildSettings = {"baseCSSPrefix":"x4-","scopeResetCSS":true};
21 Ext.isSandboxed = true;
22 /*
23
24 This file is part of Ext JS 4
25
26 Copyright (c) 2011 Sencha Inc
27
28 Contact:  http://www.sencha.com/contact
29
30 GNU General Public License Usage
31 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.
32
33 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
34
35 */
36 /**
37  * @class Ext
38  * @singleton
39  */
40 (function() {
41     var global = this,
42         objectPrototype = Object.prototype,
43         toString = objectPrototype.toString,
44         enumerables = true,
45         enumerablesTest = { toString: 1 },
46         i;
47
48     if (typeof Ext === 'undefined') {
49         global.Ext = {};
50     }
51
52     Ext.global = global;
53
54     for (i in enumerablesTest) {
55         enumerables = null;
56     }
57
58     if (enumerables) {
59         enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
60                        'toLocaleString', 'toString', 'constructor'];
61     }
62
63     /**
64      * An array containing extra enumerables for old browsers
65      * @property {String[]}
66      */
67     Ext.enumerables = enumerables;
68
69     /**
70      * Copies all the properties of config to the specified object.
71      * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
72      * {@link Ext.Object#merge} instead.
73      * @param {Object} object The receiver of the properties
74      * @param {Object} config The source of the properties
75      * @param {Object} defaults A different object that will also be applied for default values
76      * @return {Object} returns obj
77      */
78     Ext.apply = function(object, config, defaults) {
79         if (defaults) {
80             Ext.apply(object, defaults);
81         }
82
83         if (object && config && typeof config === 'object') {
84             var i, j, k;
85
86             for (i in config) {
87                 object[i] = config[i];
88             }
89
90             if (enumerables) {
91                 for (j = enumerables.length; j--;) {
92                     k = enumerables[j];
93                     if (config.hasOwnProperty(k)) {
94                         object[k] = config[k];
95                     }
96                 }
97             }
98         }
99
100         return object;
101     };
102
103     Ext.buildSettings = Ext.apply({
104         baseCSSPrefix: 'x-',
105         scopeResetCSS: false
106     }, Ext.buildSettings || {});
107
108     Ext.apply(Ext, {
109         /**
110          * A reusable empty function
111          */
112         emptyFn: function() {},
113
114         baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
115
116         /**
117          * Copies all the properties of config to object if they don't already exist.
118          * @param {Object} object The receiver of the properties
119          * @param {Object} config The source of the properties
120          * @return {Object} returns obj
121          */
122         applyIf: function(object, config) {
123             var property;
124
125             if (object) {
126                 for (property in config) {
127                     if (object[property] === undefined) {
128                         object[property] = config[property];
129                     }
130                 }
131             }
132
133             return object;
134         },
135
136         /**
137          * Iterates either an array or an object. This method delegates to
138          * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
139          *
140          * @param {Object/Array} object The object or array to be iterated.
141          * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
142          * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
143          * type that is being iterated.
144          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
145          * Defaults to the object being iterated itself.
146          * @markdown
147          */
148         iterate: function(object, fn, scope) {
149             if (Ext.isEmpty(object)) {
150                 return;
151             }
152
153             if (scope === undefined) {
154                 scope = object;
155             }
156
157             if (Ext.isIterable(object)) {
158                 Ext.Array.each.call(Ext.Array, object, fn, scope);
159             }
160             else {
161                 Ext.Object.each.call(Ext.Object, object, fn, scope);
162             }
163         }
164     });
165
166     Ext.apply(Ext, {
167
168         /**
169          * This method deprecated. Use {@link Ext#define Ext.define} instead.
170          * @method
171          * @param {Function} superclass
172          * @param {Object} overrides
173          * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
174          * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
175          */
176         extend: function() {
177             // inline overrides
178             var objectConstructor = objectPrototype.constructor,
179                 inlineOverrides = function(o) {
180                 for (var m in o) {
181                     if (!o.hasOwnProperty(m)) {
182                         continue;
183                     }
184                     this[m] = o[m];
185                 }
186             };
187
188             return function(subclass, superclass, overrides) {
189                 // First we check if the user passed in just the superClass with overrides
190                 if (Ext.isObject(superclass)) {
191                     overrides = superclass;
192                     superclass = subclass;
193                     subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
194                         superclass.apply(this, arguments);
195                     };
196                 }
197
198                 if (!superclass) {
199                     Ext.Error.raise({
200                         sourceClass: 'Ext',
201                         sourceMethod: 'extend',
202                         msg: 'Attempting to extend from a class which has not been loaded on the page.'
203                     });
204                 }
205
206                 // We create a new temporary class
207                 var F = function() {},
208                     subclassProto, superclassProto = superclass.prototype;
209
210                 F.prototype = superclassProto;
211                 subclassProto = subclass.prototype = new F();
212                 subclassProto.constructor = subclass;
213                 subclass.superclass = superclassProto;
214
215                 if (superclassProto.constructor === objectConstructor) {
216                     superclassProto.constructor = superclass;
217                 }
218
219                 subclass.override = function(overrides) {
220                     Ext.override(subclass, overrides);
221                 };
222
223                 subclassProto.override = inlineOverrides;
224                 subclassProto.proto = subclassProto;
225
226                 subclass.override(overrides);
227                 subclass.extend = function(o) {
228                     return Ext.extend(subclass, o);
229                 };
230
231                 return subclass;
232             };
233         }(),
234
235         /**
236          * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
237
238     Ext.define('My.cool.Class', {
239         sayHi: function() {
240             alert('Hi!');
241         }
242     }
243
244     Ext.override(My.cool.Class, {
245         sayHi: function() {
246             alert('About to say...');
247
248             this.callOverridden();
249         }
250     });
251
252     var cool = new My.cool.Class();
253     cool.sayHi(); // alerts 'About to say...'
254                   // alerts 'Hi!'
255
256          * Please note that `this.callOverridden()` only works if the class was previously
257          * created with {@link Ext#define)
258          *
259          * @param {Object} cls The class to override
260          * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
261          * containing one or more methods.
262          * @method override
263          * @markdown
264          */
265         override: function(cls, overrides) {
266             if (cls.prototype.$className) {
267                 return cls.override(overrides);
268             }
269             else {
270                 Ext.apply(cls.prototype, overrides);
271             }
272         }
273     });
274
275     // A full set of static methods to do type checking
276     Ext.apply(Ext, {
277
278         /**
279          * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
280          * value (second argument) otherwise.
281          *
282          * @param {Object} value The value to test
283          * @param {Object} defaultValue The value to return if the original value is empty
284          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
285          * @return {Object} value, if non-empty, else defaultValue
286          */
287         valueFrom: function(value, defaultValue, allowBlank){
288             return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
289         },
290
291         /**
292          * Returns the type of the given variable in string format. List of possible values are:
293          *
294          * - `undefined`: If the given value is `undefined`
295          * - `null`: If the given value is `null`
296          * - `string`: If the given value is a string
297          * - `number`: If the given value is a number
298          * - `boolean`: If the given value is a boolean value
299          * - `date`: If the given value is a `Date` object
300          * - `function`: If the given value is a function reference
301          * - `object`: If the given value is an object
302          * - `array`: If the given value is an array
303          * - `regexp`: If the given value is a regular expression
304          * - `element`: If the given value is a DOM Element
305          * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
306          * - `whitespace`: If the given value is a DOM text node and contains only whitespace
307          *
308          * @param {Object} value
309          * @return {String}
310          * @markdown
311          */
312         typeOf: function(value) {
313             if (value === null) {
314                 return 'null';
315             }
316
317             var type = typeof value;
318
319             if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
320                 return type;
321             }
322
323             var typeToString = toString.call(value);
324
325             switch(typeToString) {
326                 case '[object Array]':
327                     return 'array';
328                 case '[object Date]':
329                     return 'date';
330                 case '[object Boolean]':
331                     return 'boolean';
332                 case '[object Number]':
333                     return 'number';
334                 case '[object RegExp]':
335                     return 'regexp';
336             }
337
338             if (type === 'function') {
339                 return 'function';
340             }
341
342             if (type === 'object') {
343                 if (value.nodeType !== undefined) {
344                     if (value.nodeType === 3) {
345                         return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
346                     }
347                     else {
348                         return 'element';
349                     }
350                 }
351
352                 return 'object';
353             }
354
355             Ext.Error.raise({
356                 sourceClass: 'Ext',
357                 sourceMethod: 'typeOf',
358                 msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
359             });
360         },
361
362         /**
363          * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
364          *
365          * - `null`
366          * - `undefined`
367          * - a zero-length array
368          * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
369          *
370          * @param {Object} value The value to test
371          * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
372          * @return {Boolean}
373          * @markdown
374          */
375         isEmpty: function(value, allowEmptyString) {
376             return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
377         },
378
379         /**
380          * Returns true if the passed value is a JavaScript Array, false otherwise.
381          *
382          * @param {Object} target The target to test
383          * @return {Boolean}
384          * @method
385          */
386         isArray: ('isArray' in Array) ? Array.isArray : function(value) {
387             return toString.call(value) === '[object Array]';
388         },
389
390         /**
391          * Returns true if the passed value is a JavaScript Date object, false otherwise.
392          * @param {Object} object The object to test
393          * @return {Boolean}
394          */
395         isDate: function(value) {
396             return toString.call(value) === '[object Date]';
397         },
398
399         /**
400          * Returns true if the passed value is a JavaScript Object, false otherwise.
401          * @param {Object} value The value to test
402          * @return {Boolean}
403          * @method
404          */
405         isObject: (toString.call(null) === '[object Object]') ?
406         function(value) {
407             // check ownerDocument here as well to exclude DOM nodes
408             return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
409         } :
410         function(value) {
411             return toString.call(value) === '[object Object]';
412         },
413
414         /**
415          * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
416          * @param {Object} value The value to test
417          * @return {Boolean}
418          */
419         isPrimitive: function(value) {
420             var type = typeof value;
421
422             return type === 'string' || type === 'number' || type === 'boolean';
423         },
424
425         /**
426          * Returns true if the passed value is a JavaScript Function, false otherwise.
427          * @param {Object} value The value to test
428          * @return {Boolean}
429          * @method
430          */
431         isFunction:
432         // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
433         // Object.prorotype.toString (slower)
434         (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
435             return toString.call(value) === '[object Function]';
436         } : function(value) {
437             return typeof value === 'function';
438         },
439
440         /**
441          * Returns true if the passed value is a number. Returns false for non-finite numbers.
442          * @param {Object} value The value to test
443          * @return {Boolean}
444          */
445         isNumber: function(value) {
446             return typeof value === 'number' && isFinite(value);
447         },
448
449         /**
450          * Validates that a value is numeric.
451          * @param {Object} value Examples: 1, '1', '2.34'
452          * @return {Boolean} True if numeric, false otherwise
453          */
454         isNumeric: function(value) {
455             return !isNaN(parseFloat(value)) && isFinite(value);
456         },
457
458         /**
459          * Returns true if the passed value is a string.
460          * @param {Object} value The value to test
461          * @return {Boolean}
462          */
463         isString: function(value) {
464             return typeof value === 'string';
465         },
466
467         /**
468          * Returns true if the passed value is a boolean.
469          *
470          * @param {Object} value The value to test
471          * @return {Boolean}
472          */
473         isBoolean: function(value) {
474             return typeof value === 'boolean';
475         },
476
477         /**
478          * Returns true if the passed value is an HTMLElement
479          * @param {Object} value The value to test
480          * @return {Boolean}
481          */
482         isElement: function(value) {
483             return value ? value.nodeType === 1 : false;
484         },
485
486         /**
487          * Returns true if the passed value is a TextNode
488          * @param {Object} value The value to test
489          * @return {Boolean}
490          */
491         isTextNode: function(value) {
492             return value ? value.nodeName === "#text" : false;
493         },
494
495         /**
496          * Returns true if the passed value is defined.
497          * @param {Object} value The value to test
498          * @return {Boolean}
499          */
500         isDefined: function(value) {
501             return typeof value !== 'undefined';
502         },
503
504         /**
505          * Returns true if the passed value is iterable, false otherwise
506          * @param {Object} value The value to test
507          * @return {Boolean}
508          */
509         isIterable: function(value) {
510             return (value && typeof value !== 'string') ? value.length !== undefined : false;
511         }
512     });
513
514     Ext.apply(Ext, {
515
516         /**
517          * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
518          * @param {Object} item The variable to clone
519          * @return {Object} clone
520          */
521         clone: function(item) {
522             if (item === null || item === undefined) {
523                 return item;
524             }
525
526             // DOM nodes
527             // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
528             // recursively
529             if (item.nodeType && item.cloneNode) {
530                 return item.cloneNode(true);
531             }
532
533             var type = toString.call(item);
534
535             // Date
536             if (type === '[object Date]') {
537                 return new Date(item.getTime());
538             }
539
540             var i, j, k, clone, key;
541
542             // Array
543             if (type === '[object Array]') {
544                 i = item.length;
545
546                 clone = [];
547
548                 while (i--) {
549                     clone[i] = Ext.clone(item[i]);
550                 }
551             }
552             // Object
553             else if (type === '[object Object]' && item.constructor === Object) {
554                 clone = {};
555
556                 for (key in item) {
557                     clone[key] = Ext.clone(item[key]);
558                 }
559
560                 if (enumerables) {
561                     for (j = enumerables.length; j--;) {
562                         k = enumerables[j];
563                         clone[k] = item[k];
564                     }
565                 }
566             }
567
568             return clone || item;
569         },
570
571         /**
572          * @private
573          * Generate a unique reference of Ext in the global scope, useful for sandboxing
574          */
575         getUniqueGlobalNamespace: function() {
576             var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
577
578             if (uniqueGlobalNamespace === undefined) {
579                 var i = 0;
580
581                 do {
582                     uniqueGlobalNamespace = 'ExtBox' + (++i);
583                 } while (Ext.global[uniqueGlobalNamespace] !== undefined);
584
585                 Ext.global[uniqueGlobalNamespace] = Ext;
586                 this.uniqueGlobalNamespace = uniqueGlobalNamespace;
587             }
588
589             return uniqueGlobalNamespace;
590         },
591
592         /**
593          * @private
594          */
595         functionFactory: function() {
596             var args = Array.prototype.slice.call(arguments);
597
598             if (args.length > 0) {
599                 args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
600                     args[args.length - 1];
601             }
602
603             return Function.prototype.constructor.apply(Function.prototype, args);
604         }
605     });
606
607     /**
608      * Old alias to {@link Ext#typeOf}
609      * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
610      * @method
611      * @alias Ext#typeOf
612      */
613     Ext.type = Ext.typeOf;
614
615 })();
616
617 /**
618  * @author Jacky Nguyen <jacky@sencha.com>
619  * @docauthor Jacky Nguyen <jacky@sencha.com>
620  * @class Ext.Version
621  *
622  * A utility class that wrap around a string version number and provide convenient
623  * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
624
625     var version = new Ext.Version('1.0.2beta');
626     console.log("Version is " + version); // Version is 1.0.2beta
627
628     console.log(version.getMajor()); // 1
629     console.log(version.getMinor()); // 0
630     console.log(version.getPatch()); // 2
631     console.log(version.getBuild()); // 0
632     console.log(version.getRelease()); // beta
633
634     console.log(version.isGreaterThan('1.0.1')); // True
635     console.log(version.isGreaterThan('1.0.2alpha')); // True
636     console.log(version.isGreaterThan('1.0.2RC')); // False
637     console.log(version.isGreaterThan('1.0.2')); // False
638     console.log(version.isLessThan('1.0.2')); // True
639
640     console.log(version.match(1.0)); // True
641     console.log(version.match('1.0.2')); // True
642
643  * @markdown
644  */
645 (function() {
646
647 // Current core version
648 var version = '4.0.7', Version;
649     Ext.Version = Version = Ext.extend(Object, {
650
651         /**
652          * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
653          * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
654          * @return {Ext.Version} this
655          */
656         constructor: function(version) {
657             var parts, releaseStartIndex;
658
659             if (version instanceof Version) {
660                 return version;
661             }
662
663             this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
664
665             releaseStartIndex = this.version.search(/([^\d\.])/);
666
667             if (releaseStartIndex !== -1) {
668                 this.release = this.version.substr(releaseStartIndex, version.length);
669                 this.shortVersion = this.version.substr(0, releaseStartIndex);
670             }
671
672             this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
673
674             parts = this.version.split('.');
675
676             this.major = parseInt(parts.shift() || 0, 10);
677             this.minor = parseInt(parts.shift() || 0, 10);
678             this.patch = parseInt(parts.shift() || 0, 10);
679             this.build = parseInt(parts.shift() || 0, 10);
680
681             return this;
682         },
683
684         /**
685          * Override the native toString method
686          * @private
687          * @return {String} version
688          */
689         toString: function() {
690             return this.version;
691         },
692
693         /**
694          * Override the native valueOf method
695          * @private
696          * @return {String} version
697          */
698         valueOf: function() {
699             return this.version;
700         },
701
702         /**
703          * Returns the major component value
704          * @return {Number} major
705          */
706         getMajor: function() {
707             return this.major || 0;
708         },
709
710         /**
711          * Returns the minor component value
712          * @return {Number} minor
713          */
714         getMinor: function() {
715             return this.minor || 0;
716         },
717
718         /**
719          * Returns the patch component value
720          * @return {Number} patch
721          */
722         getPatch: function() {
723             return this.patch || 0;
724         },
725
726         /**
727          * Returns the build component value
728          * @return {Number} build
729          */
730         getBuild: function() {
731             return this.build || 0;
732         },
733
734         /**
735          * Returns the release component value
736          * @return {Number} release
737          */
738         getRelease: function() {
739             return this.release || '';
740         },
741
742         /**
743          * Returns whether this version if greater than the supplied argument
744          * @param {String/Number} target The version to compare with
745          * @return {Boolean} True if this version if greater than the target, false otherwise
746          */
747         isGreaterThan: function(target) {
748             return Version.compare(this.version, target) === 1;
749         },
750
751         /**
752          * Returns whether this version if smaller than the supplied argument
753          * @param {String/Number} target The version to compare with
754          * @return {Boolean} True if this version if smaller than the target, false otherwise
755          */
756         isLessThan: function(target) {
757             return Version.compare(this.version, target) === -1;
758         },
759
760         /**
761          * Returns whether this version equals to the supplied argument
762          * @param {String/Number} target The version to compare with
763          * @return {Boolean} True if this version equals to the target, false otherwise
764          */
765         equals: function(target) {
766             return Version.compare(this.version, target) === 0;
767         },
768
769         /**
770          * Returns whether this version matches the supplied argument. Example:
771          * <pre><code>
772          * var version = new Ext.Version('1.0.2beta');
773          * console.log(version.match(1)); // True
774          * console.log(version.match(1.0)); // True
775          * console.log(version.match('1.0.2')); // True
776          * console.log(version.match('1.0.2RC')); // False
777          * </code></pre>
778          * @param {String/Number} target The version to compare with
779          * @return {Boolean} True if this version matches the target, false otherwise
780          */
781         match: function(target) {
782             target = String(target);
783             return this.version.substr(0, target.length) === target;
784         },
785
786         /**
787          * Returns this format: [major, minor, patch, build, release]. Useful for comparison
788          * @return {Number[]}
789          */
790         toArray: function() {
791             return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
792         },
793
794         /**
795          * Returns shortVersion version without dots and release
796          * @return {String}
797          */
798         getShortVersion: function() {
799             return this.shortVersion;
800         }
801     });
802
803     Ext.apply(Version, {
804         // @private
805         releaseValueMap: {
806             'dev': -6,
807             'alpha': -5,
808             'a': -5,
809             'beta': -4,
810             'b': -4,
811             'rc': -3,
812             '#': -2,
813             'p': -1,
814             'pl': -1
815         },
816
817         /**
818          * Converts a version component to a comparable value
819          *
820          * @static
821          * @param {Object} value The value to convert
822          * @return {Object}
823          */
824         getComponentValue: function(value) {
825             return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
826         },
827
828         /**
829          * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
830          * they are handled in the following order:
831          * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
832          *
833          * @static
834          * @param {String} current The current version to compare to
835          * @param {String} target The target version to compare to
836          * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent
837          */
838         compare: function(current, target) {
839             var currentValue, targetValue, i;
840
841             current = new Version(current).toArray();
842             target = new Version(target).toArray();
843
844             for (i = 0; i < Math.max(current.length, target.length); i++) {
845                 currentValue = this.getComponentValue(current[i]);
846                 targetValue = this.getComponentValue(target[i]);
847
848                 if (currentValue < targetValue) {
849                     return -1;
850                 } else if (currentValue > targetValue) {
851                     return 1;
852                 }
853             }
854
855             return 0;
856         }
857     });
858
859     Ext.apply(Ext, {
860         /**
861          * @private
862          */
863         versions: {},
864
865         /**
866          * @private
867          */
868         lastRegisteredVersion: null,
869
870         /**
871          * Set version number for the given package name.
872          *
873          * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'
874          * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'
875          * @return {Ext}
876          */
877         setVersion: function(packageName, version) {
878             Ext.versions[packageName] = new Version(version);
879             Ext.lastRegisteredVersion = Ext.versions[packageName];
880
881             return this;
882         },
883
884         /**
885          * Get the version number of the supplied package name; will return the last registered version
886          * (last Ext.setVersion call) if there's no package name given.
887          *
888          * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'
889          * @return {Ext.Version} The version
890          */
891         getVersion: function(packageName) {
892             if (packageName === undefined) {
893                 return Ext.lastRegisteredVersion;
894             }
895
896             return Ext.versions[packageName];
897         },
898
899         /**
900          * Create a closure for deprecated code.
901          *
902     // This means Ext.oldMethod is only supported in 4.0.0beta and older.
903     // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
904     // the closure will not be invoked
905     Ext.deprecate('extjs', '4.0.0beta', function() {
906         Ext.oldMethod = Ext.newMethod;
907
908         ...
909     });
910
911          * @param {String} packageName The package name
912          * @param {String} since The last version before it's deprecated
913          * @param {Function} closure The callback function to be executed with the specified version is less than the current version
914          * @param {Object} scope The execution scope (<tt>this</tt>) if the closure
915          * @markdown
916          */
917         deprecate: function(packageName, since, closure, scope) {
918             if (Version.compare(Ext.getVersion(packageName), since) < 1) {
919                 closure.call(scope);
920             }
921         }
922     }); // End Versioning
923
924     Ext.setVersion('core', version);
925
926 })();
927
928 /**
929  * @class Ext.String
930  *
931  * A collection of useful static methods to deal with strings
932  * @singleton
933  */
934
935 Ext.String = {
936     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,
937     escapeRe: /('|\\)/g,
938     formatRe: /\{(\d+)\}/g,
939     escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
940
941     /**
942      * Convert certain characters (&, <, >, and ") to their HTML character equivalents for literal display in web pages.
943      * @param {String} value The string to encode
944      * @return {String} The encoded text
945      * @method
946      */
947     htmlEncode: (function() {
948         var entities = {
949             '&': '&amp;',
950             '>': '&gt;',
951             '<': '&lt;',
952             '"': '&quot;'
953         }, keys = [], p, regex;
954         
955         for (p in entities) {
956             keys.push(p);
957         }
958         
959         regex = new RegExp('(' + keys.join('|') + ')', 'g');
960         
961         return function(value) {
962             return (!value) ? value : String(value).replace(regex, function(match, capture) {
963                 return entities[capture];    
964             });
965         };
966     })(),
967
968     /**
969      * Convert certain characters (&, <, >, and ") from their HTML character equivalents.
970      * @param {String} value The string to decode
971      * @return {String} The decoded text
972      * @method
973      */
974     htmlDecode: (function() {
975         var entities = {
976             '&amp;': '&',
977             '&gt;': '>',
978             '&lt;': '<',
979             '&quot;': '"'
980         }, keys = [], p, regex;
981         
982         for (p in entities) {
983             keys.push(p);
984         }
985         
986         regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
987         
988         return function(value) {
989             return (!value) ? value : String(value).replace(regex, function(match, capture) {
990                 if (capture in entities) {
991                     return entities[capture];
992                 } else {
993                     return String.fromCharCode(parseInt(capture.substr(2), 10));
994                 }
995             });
996         };
997     })(),
998
999     /**
1000      * Appends content to the query string of a URL, handling logic for whether to place
1001      * a question mark or ampersand.
1002      * @param {String} url The URL to append to.
1003      * @param {String} string The content to append to the URL.
1004      * @return (String) The resulting URL
1005      */
1006     urlAppend : function(url, string) {
1007         if (!Ext.isEmpty(string)) {
1008             return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
1009         }
1010
1011         return url;
1012     },
1013
1014     /**
1015      * Trims whitespace from either end of a string, leaving spaces within the string intact.  Example:
1016      * @example
1017 var s = '  foo bar  ';
1018 alert('-' + s + '-');         //alerts "- foo bar -"
1019 alert('-' + Ext.String.trim(s) + '-');  //alerts "-foo bar-"
1020
1021      * @param {String} string The string to escape
1022      * @return {String} The trimmed string
1023      */
1024     trim: function(string) {
1025         return string.replace(Ext.String.trimRegex, "");
1026     },
1027
1028     /**
1029      * Capitalize the given string
1030      * @param {String} string
1031      * @return {String}
1032      */
1033     capitalize: function(string) {
1034         return string.charAt(0).toUpperCase() + string.substr(1);
1035     },
1036
1037     /**
1038      * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
1039      * @param {String} value The string to truncate
1040      * @param {Number} length The maximum length to allow before truncating
1041      * @param {Boolean} word True to try to find a common word break
1042      * @return {String} The converted text
1043      */
1044     ellipsis: function(value, len, word) {
1045         if (value && value.length > len) {
1046             if (word) {
1047                 var vs = value.substr(0, len - 2),
1048                 index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
1049                 if (index !== -1 && index >= (len - 15)) {
1050                     return vs.substr(0, index) + "...";
1051                 }
1052             }
1053             return value.substr(0, len - 3) + "...";
1054         }
1055         return value;
1056     },
1057
1058     /**
1059      * Escapes the passed string for use in a regular expression
1060      * @param {String} string
1061      * @return {String}
1062      */
1063     escapeRegex: function(string) {
1064         return string.replace(Ext.String.escapeRegexRe, "\\$1");
1065     },
1066
1067     /**
1068      * Escapes the passed string for ' and \
1069      * @param {String} string The string to escape
1070      * @return {String} The escaped string
1071      */
1072     escape: function(string) {
1073         return string.replace(Ext.String.escapeRe, "\\$1");
1074     },
1075
1076     /**
1077      * Utility function that allows you to easily switch a string between two alternating values.  The passed value
1078      * is compared to the current string, and if they are equal, the other value that was passed in is returned.  If
1079      * they are already different, the first value passed in is returned.  Note that this method returns the new value
1080      * but does not change the current string.
1081      * <pre><code>
1082     // alternate sort directions
1083     sort = Ext.String.toggle(sort, 'ASC', 'DESC');
1084
1085     // instead of conditional logic:
1086     sort = (sort == 'ASC' ? 'DESC' : 'ASC');
1087        </code></pre>
1088      * @param {String} string The current string
1089      * @param {String} value The value to compare to the current string
1090      * @param {String} other The new value to use if the string already equals the first value passed in
1091      * @return {String} The new value
1092      */
1093     toggle: function(string, value, other) {
1094         return string === value ? other : value;
1095     },
1096
1097     /**
1098      * Pads the left side of a string with a specified character.  This is especially useful
1099      * for normalizing number and date strings.  Example usage:
1100      *
1101      * <pre><code>
1102 var s = Ext.String.leftPad('123', 5, '0');
1103 // s now contains the string: '00123'
1104        </code></pre>
1105      * @param {String} string The original string
1106      * @param {Number} size The total length of the output string
1107      * @param {String} character (optional) The character with which to pad the original string (defaults to empty string " ")
1108      * @return {String} The padded string
1109      */
1110     leftPad: function(string, size, character) {
1111         var result = String(string);
1112         character = character || " ";
1113         while (result.length < size) {
1114             result = character + result;
1115         }
1116         return result;
1117     },
1118
1119     /**
1120      * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens.  Each
1121      * token must be unique, and must increment in the format {0}, {1}, etc.  Example usage:
1122      * <pre><code>
1123 var cls = 'my-class', text = 'Some text';
1124 var s = Ext.String.format('&lt;div class="{0}">{1}&lt;/div>', cls, text);
1125 // s now contains the string: '&lt;div class="my-class">Some text&lt;/div>'
1126        </code></pre>
1127      * @param {String} string The tokenized string to be formatted
1128      * @param {String} value1 The value to replace token {0}
1129      * @param {String} value2 Etc...
1130      * @return {String} The formatted string
1131      */
1132     format: function(format) {
1133         var args = Ext.Array.toArray(arguments, 1);
1134         return format.replace(Ext.String.formatRe, function(m, i) {
1135             return args[i];
1136         });
1137     },
1138
1139     /**
1140      * Returns a string with a specified number of repititions a given string pattern.
1141      * The pattern be separated by a different string.
1142      *
1143      *      var s = Ext.String.repeat('---', 4); // = '------------'
1144      *      var t = Ext.String.repeat('--', 3, '/'); // = '--/--/--'
1145      *
1146      * @param {String} pattern The pattern to repeat.
1147      * @param {Number} count The number of times to repeat the pattern (may be 0).
1148      * @param {String} sep An option string to separate each pattern.
1149      */
1150     repeat: function(pattern, count, sep) {
1151         for (var buf = [], i = count; i--; ) {
1152             buf.push(pattern);
1153         }
1154         return buf.join(sep || '');
1155     }
1156 };
1157
1158 /**
1159  * @class Ext.Number
1160  *
1161  * A collection of useful static methods to deal with numbers
1162  * @singleton
1163  */
1164
1165 (function() {
1166
1167 var isToFixedBroken = (0.9).toFixed() !== '1';
1168
1169 Ext.Number = {
1170     /**
1171      * Checks whether or not the passed number is within a desired range.  If the number is already within the
1172      * range it is returned, otherwise the min or max value is returned depending on which side of the range is
1173      * exceeded. Note that this method returns the constrained value but does not change the current number.
1174      * @param {Number} number The number to check
1175      * @param {Number} min The minimum number in the range
1176      * @param {Number} max The maximum number in the range
1177      * @return {Number} The constrained value if outside the range, otherwise the current value
1178      */
1179     constrain: function(number, min, max) {
1180         number = parseFloat(number);
1181
1182         if (!isNaN(min)) {
1183             number = Math.max(number, min);
1184         }
1185         if (!isNaN(max)) {
1186             number = Math.min(number, max);
1187         }
1188         return number;
1189     },
1190
1191     /**
1192      * Snaps the passed number between stopping points based upon a passed increment value.
1193      * @param {Number} value The unsnapped value.
1194      * @param {Number} increment The increment by which the value must move.
1195      * @param {Number} minValue The minimum value to which the returned value must be constrained. Overrides the increment..
1196      * @param {Number} maxValue The maximum value to which the returned value must be constrained. Overrides the increment..
1197      * @return {Number} The value of the nearest snap target.
1198      */
1199     snap : function(value, increment, minValue, maxValue) {
1200         var newValue = value,
1201             m;
1202
1203         if (!(increment && value)) {
1204             return value;
1205         }
1206         m = value % increment;
1207         if (m !== 0) {
1208             newValue -= m;
1209             if (m * 2 >= increment) {
1210                 newValue += increment;
1211             } else if (m * 2 < -increment) {
1212                 newValue -= increment;
1213             }
1214         }
1215         return Ext.Number.constrain(newValue, minValue,  maxValue);
1216     },
1217
1218     /**
1219      * Formats a number using fixed-point notation
1220      * @param {Number} value The number to format
1221      * @param {Number} precision The number of digits to show after the decimal point
1222      */
1223     toFixed: function(value, precision) {
1224         if (isToFixedBroken) {
1225             precision = precision || 0;
1226             var pow = Math.pow(10, precision);
1227             return (Math.round(value * pow) / pow).toFixed(precision);
1228         }
1229
1230         return value.toFixed(precision);
1231     },
1232
1233     /**
1234      * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
1235      * it is not.
1236
1237 Ext.Number.from('1.23', 1); // returns 1.23
1238 Ext.Number.from('abc', 1); // returns 1
1239
1240      * @param {Object} value
1241      * @param {Number} defaultValue The value to return if the original value is non-numeric
1242      * @return {Number} value, if numeric, defaultValue otherwise
1243      */
1244     from: function(value, defaultValue) {
1245         if (isFinite(value)) {
1246             value = parseFloat(value);
1247         }
1248
1249         return !isNaN(value) ? value : defaultValue;
1250     }
1251 };
1252
1253 })();
1254
1255 /**
1256  * @deprecated 4.0.0 Please use {@link Ext.Number#from} instead.
1257  * @member Ext
1258  * @method num
1259  * @alias Ext.Number#from
1260  */
1261 Ext.num = function() {
1262     return Ext.Number.from.apply(this, arguments);
1263 };
1264 /**
1265  * @class Ext.Array
1266  * @singleton
1267  * @author Jacky Nguyen <jacky@sencha.com>
1268  * @docauthor Jacky Nguyen <jacky@sencha.com>
1269  *
1270  * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
1271  */
1272 (function() {
1273
1274     var arrayPrototype = Array.prototype,
1275         slice = arrayPrototype.slice,
1276         supportsSplice = function () {
1277             var array = [],
1278                 lengthBefore,
1279                 j = 20;
1280
1281             if (!array.splice) {
1282                 return false;
1283             }
1284
1285             // This detects a bug in IE8 splice method:
1286             // see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/
1287
1288             while (j--) {
1289                 array.push("A");
1290             }
1291
1292             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");
1293
1294             lengthBefore = array.length; //41
1295             array.splice(13, 0, "XXX"); // add one element
1296
1297             if (lengthBefore+1 != array.length) {
1298                 return false;
1299             }
1300             // end IE8 bug
1301
1302             return true;
1303         }(),
1304         supportsForEach = 'forEach' in arrayPrototype,
1305         supportsMap = 'map' in arrayPrototype,
1306         supportsIndexOf = 'indexOf' in arrayPrototype,
1307         supportsEvery = 'every' in arrayPrototype,
1308         supportsSome = 'some' in arrayPrototype,
1309         supportsFilter = 'filter' in arrayPrototype,
1310         supportsSort = function() {
1311             var a = [1,2,3,4,5].sort(function(){ return 0; });
1312             return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
1313         }(),
1314         supportsSliceOnNodeList = true,
1315         ExtArray;
1316
1317     try {
1318         // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
1319         if (typeof document !== 'undefined') {
1320             slice.call(document.getElementsByTagName('body'));
1321         }
1322     } catch (e) {
1323         supportsSliceOnNodeList = false;
1324     }
1325
1326     function fixArrayIndex (array, index) {
1327         return (index < 0) ? Math.max(0, array.length + index)
1328                            : Math.min(array.length, index);
1329     }
1330
1331     /*
1332     Does the same work as splice, but with a slightly more convenient signature. The splice
1333     method has bugs in IE8, so this is the implementation we use on that platform.
1334
1335     The rippling of items in the array can be tricky. Consider two use cases:
1336
1337                   index=2
1338                   removeCount=2
1339                  /=====\
1340         +---+---+---+---+---+---+---+---+
1341         | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1342         +---+---+---+---+---+---+---+---+
1343                          /  \/  \/  \/  \
1344                         /   /\  /\  /\   \
1345                        /   /  \/  \/  \   +--------------------------+
1346                       /   /   /\  /\   +--------------------------+   \
1347                      /   /   /  \/  +--------------------------+   \   \
1348                     /   /   /   /+--------------------------+   \   \   \
1349                    /   /   /   /                             \   \   \   \
1350                   v   v   v   v                               v   v   v   v
1351         +---+---+---+---+---+---+       +---+---+---+---+---+---+---+---+---+
1352         | 0 | 1 | 4 | 5 | 6 | 7 |       | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
1353         +---+---+---+---+---+---+       +---+---+---+---+---+---+---+---+---+
1354         A                               B        \=========/
1355                                                  insert=[a,b,c]
1356
1357     In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
1358     that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
1359     must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
1360     */
1361     function replaceSim (array, index, removeCount, insert) {
1362         var add = insert ? insert.length : 0,
1363             length = array.length,
1364             pos = fixArrayIndex(array, index);
1365
1366         // we try to use Array.push when we can for efficiency...
1367         if (pos === length) {
1368             if (add) {
1369                 array.push.apply(array, insert);
1370             }
1371         } else {
1372             var remove = Math.min(removeCount, length - pos),
1373                 tailOldPos = pos + remove,
1374                 tailNewPos = tailOldPos + add - remove,
1375                 tailCount = length - tailOldPos,
1376                 lengthAfterRemove = length - remove,
1377                 i;
1378
1379             if (tailNewPos < tailOldPos) { // case A
1380                 for (i = 0; i < tailCount; ++i) {
1381                     array[tailNewPos+i] = array[tailOldPos+i];
1382                 }
1383             } else if (tailNewPos > tailOldPos) { // case B
1384                 for (i = tailCount; i--; ) {
1385                     array[tailNewPos+i] = array[tailOldPos+i];
1386                 }
1387             } // else, add == remove (nothing to do)
1388
1389             if (add && pos === lengthAfterRemove) {
1390                 array.length = lengthAfterRemove; // truncate array
1391                 array.push.apply(array, insert);
1392             } else {
1393                 array.length = lengthAfterRemove + add; // reserves space
1394                 for (i = 0; i < add; ++i) {
1395                     array[pos+i] = insert[i];
1396                 }
1397             }
1398         }
1399
1400         return array;
1401     }
1402
1403     function replaceNative (array, index, removeCount, insert) {
1404         if (insert && insert.length) {
1405             if (index < array.length) {
1406                 array.splice.apply(array, [index, removeCount].concat(insert));
1407             } else {
1408                 array.push.apply(array, insert);
1409             }
1410         } else {
1411             array.splice(index, removeCount);
1412         }
1413         return array;
1414     }
1415
1416     function eraseSim (array, index, removeCount) {
1417         return replaceSim(array, index, removeCount);
1418     }
1419
1420     function eraseNative (array, index, removeCount) {
1421         array.splice(index, removeCount);
1422         return array;
1423     }
1424
1425     function spliceSim (array, index, removeCount) {
1426         var pos = fixArrayIndex(array, index),
1427             removed = array.slice(index, fixArrayIndex(array, pos+removeCount));
1428
1429         if (arguments.length < 4) {
1430             replaceSim(array, pos, removeCount);
1431         } else {
1432             replaceSim(array, pos, removeCount, slice.call(arguments, 3));
1433         }
1434
1435         return removed;
1436     }
1437
1438     function spliceNative (array) {
1439         return array.splice.apply(array, slice.call(arguments, 1));
1440     }
1441
1442     var erase = supportsSplice ? eraseNative : eraseSim,
1443         replace = supportsSplice ? replaceNative : replaceSim,
1444         splice = supportsSplice ? spliceNative : spliceSim;
1445
1446     // NOTE: from here on, use erase, replace or splice (not native methods)...
1447
1448     ExtArray = Ext.Array = {
1449         /**
1450          * Iterates an array or an iterable value and invoke the given callback function for each item.
1451          *
1452          *     var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
1453          *
1454          *     Ext.Array.each(countries, function(name, index, countriesItSelf) {
1455          *         console.log(name);
1456          *     });
1457          *
1458          *     var sum = function() {
1459          *         var sum = 0;
1460          *
1461          *         Ext.Array.each(arguments, function(value) {
1462          *             sum += value;
1463          *         });
1464          *
1465          *         return sum;
1466          *     };
1467          *
1468          *     sum(1, 2, 3); // returns 6
1469          *
1470          * The iteration can be stopped by returning false in the function callback.
1471          *
1472          *     Ext.Array.each(countries, function(name, index, countriesItSelf) {
1473          *         if (name === 'Singapore') {
1474          *             return false; // break here
1475          *         }
1476          *     });
1477          *
1478          * {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each}
1479          *
1480          * @param {Array/NodeList/Object} iterable The value to be iterated. If this
1481          * argument is not iterable, the callback function is called once.
1482          * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
1483          * the current `index`.
1484          * @param {Object} fn.item The item at the current `index` in the passed `array`
1485          * @param {Number} fn.index The current `index` within the `array`
1486          * @param {Array} fn.allItems The `array` itself which was passed as the first argument
1487          * @param {Boolean} fn.return Return false to stop iteration.
1488          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
1489          * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
1490          * Defaults false
1491          * @return {Boolean} See description for the `fn` parameter.
1492          */
1493         each: function(array, fn, scope, reverse) {
1494             array = ExtArray.from(array);
1495
1496             var i,
1497                 ln = array.length;
1498
1499             if (reverse !== true) {
1500                 for (i = 0; i < ln; i++) {
1501                     if (fn.call(scope || array[i], array[i], i, array) === false) {
1502                         return i;
1503                     }
1504                 }
1505             }
1506             else {
1507                 for (i = ln - 1; i > -1; i--) {
1508                     if (fn.call(scope || array[i], array[i], i, array) === false) {
1509                         return i;
1510                     }
1511                 }
1512             }
1513
1514             return true;
1515         },
1516
1517         /**
1518          * Iterates an array and invoke the given callback function for each item. Note that this will simply
1519          * delegate to the native Array.prototype.forEach method if supported. It doesn't support stopping the
1520          * iteration by returning false in the callback function like {@link Ext.Array#each}. However, performance
1521          * could be much better in modern browsers comparing with {@link Ext.Array#each}
1522          *
1523          * @param {Array} array The array to iterate
1524          * @param {Function} fn The callback function.
1525          * @param {Object} fn.item The item at the current `index` in the passed `array`
1526          * @param {Number} fn.index The current `index` within the `array`
1527          * @param {Array}  fn.allItems The `array` itself which was passed as the first argument
1528          * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
1529          */
1530         forEach: function(array, fn, scope) {
1531             if (supportsForEach) {
1532                 return array.forEach(fn, scope);
1533             }
1534
1535             var i = 0,
1536                 ln = array.length;
1537
1538             for (; i < ln; i++) {
1539                 fn.call(scope, array[i], i, array);
1540             }
1541         },
1542
1543         /**
1544          * Get the index of the provided `item` in the given `array`, a supplement for the
1545          * missing arrayPrototype.indexOf in Internet Explorer.
1546          *
1547          * @param {Array} array The array to check
1548          * @param {Object} item The item to look for
1549          * @param {Number} from (Optional) The index at which to begin the search
1550          * @return {Number} The index of item in the array (or -1 if it is not found)
1551          */
1552         indexOf: function(array, item, from) {
1553             if (supportsIndexOf) {
1554                 return array.indexOf(item, from);
1555             }
1556
1557             var i, length = array.length;
1558
1559             for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
1560                 if (array[i] === item) {
1561                     return i;
1562                 }
1563             }
1564
1565             return -1;
1566         },
1567
1568         /**
1569          * Checks whether or not the given `array` contains the specified `item`
1570          *
1571          * @param {Array} array The array to check
1572          * @param {Object} item The item to look for
1573          * @return {Boolean} True if the array contains the item, false otherwise
1574          */
1575         contains: function(array, item) {
1576             if (supportsIndexOf) {
1577                 return array.indexOf(item) !== -1;
1578             }
1579
1580             var i, ln;
1581
1582             for (i = 0, ln = array.length; i < ln; i++) {
1583                 if (array[i] === item) {
1584                     return true;
1585                 }
1586             }
1587
1588             return false;
1589         },
1590
1591         /**
1592          * Converts any iterable (numeric indices and a length property) into a true array.
1593          *
1594          *     function test() {
1595          *         var args = Ext.Array.toArray(arguments),
1596          *             fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
1597          *
1598          *         alert(args.join(' '));
1599          *         alert(fromSecondToLastArgs.join(' '));
1600          *     }
1601          *
1602          *     test('just', 'testing', 'here'); // alerts 'just testing here';
1603          *                                      // alerts 'testing here';
1604          *
1605          *     Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
1606          *     Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
1607          *     Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
1608          *
1609          * {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray}
1610          *
1611          * @param {Object} iterable the iterable object to be turned into a true Array.
1612          * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
1613          * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
1614          * index of the iterable value
1615          * @return {Array} array
1616          */
1617         toArray: function(iterable, start, end){
1618             if (!iterable || !iterable.length) {
1619                 return [];
1620             }
1621
1622             if (typeof iterable === 'string') {
1623                 iterable = iterable.split('');
1624             }
1625
1626             if (supportsSliceOnNodeList) {
1627                 return slice.call(iterable, start || 0, end || iterable.length);
1628             }
1629
1630             var array = [],
1631                 i;
1632
1633             start = start || 0;
1634             end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
1635
1636             for (i = start; i < end; i++) {
1637                 array.push(iterable[i]);
1638             }
1639
1640             return array;
1641         },
1642
1643         /**
1644          * Plucks the value of a property from each item in the Array. Example:
1645          *
1646          *     Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
1647          *
1648          * @param {Array/NodeList} array The Array of items to pluck the value from.
1649          * @param {String} propertyName The property name to pluck from each element.
1650          * @return {Array} The value from each item in the Array.
1651          */
1652         pluck: function(array, propertyName) {
1653             var ret = [],
1654                 i, ln, item;
1655
1656             for (i = 0, ln = array.length; i < ln; i++) {
1657                 item = array[i];
1658
1659                 ret.push(item[propertyName]);
1660             }
1661
1662             return ret;
1663         },
1664
1665         /**
1666          * Creates a new array with the results of calling a provided function on every element in this array.
1667          *
1668          * @param {Array} array
1669          * @param {Function} fn Callback function for each item
1670          * @param {Object} scope Callback function scope
1671          * @return {Array} results
1672          */
1673         map: function(array, fn, scope) {
1674             if (supportsMap) {
1675                 return array.map(fn, scope);
1676             }
1677
1678             var results = [],
1679                 i = 0,
1680                 len = array.length;
1681
1682             for (; i < len; i++) {
1683                 results[i] = fn.call(scope, array[i], i, array);
1684             }
1685
1686             return results;
1687         },
1688
1689         /**
1690          * Executes the specified function for each array element until the function returns a falsy value.
1691          * If such an item is found, the function will return false immediately.
1692          * Otherwise, it will return true.
1693          *
1694          * @param {Array} array
1695          * @param {Function} fn Callback function for each item
1696          * @param {Object} scope Callback function scope
1697          * @return {Boolean} True if no false value is returned by the callback function.
1698          */
1699         every: function(array, fn, scope) {
1700             if (!fn) {
1701                 Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
1702             }
1703             if (supportsEvery) {
1704                 return array.every(fn, scope);
1705             }
1706
1707             var i = 0,
1708                 ln = array.length;
1709
1710             for (; i < ln; ++i) {
1711                 if (!fn.call(scope, array[i], i, array)) {
1712                     return false;
1713                 }
1714             }
1715
1716             return true;
1717         },
1718
1719         /**
1720          * Executes the specified function for each array element until the function returns a truthy value.
1721          * If such an item is found, the function will return true immediately. Otherwise, it will return false.
1722          *
1723          * @param {Array} array
1724          * @param {Function} fn Callback function for each item
1725          * @param {Object} scope Callback function scope
1726          * @return {Boolean} True if the callback function returns a truthy value.
1727          */
1728         some: function(array, fn, scope) {
1729             if (!fn) {
1730                 Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
1731             }
1732             if (supportsSome) {
1733                 return array.some(fn, scope);
1734             }
1735
1736             var i = 0,
1737                 ln = array.length;
1738
1739             for (; i < ln; ++i) {
1740                 if (fn.call(scope, array[i], i, array)) {
1741                     return true;
1742                 }
1743             }
1744
1745             return false;
1746         },
1747
1748         /**
1749          * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
1750          *
1751          * See {@link Ext.Array#filter}
1752          *
1753          * @param {Array} array
1754          * @return {Array} results
1755          */
1756         clean: function(array) {
1757             var results = [],
1758                 i = 0,
1759                 ln = array.length,
1760                 item;
1761
1762             for (; i < ln; i++) {
1763                 item = array[i];
1764
1765                 if (!Ext.isEmpty(item)) {
1766                     results.push(item);
1767                 }
1768             }
1769
1770             return results;
1771         },
1772
1773         /**
1774          * Returns a new array with unique items
1775          *
1776          * @param {Array} array
1777          * @return {Array} results
1778          */
1779         unique: function(array) {
1780             var clone = [],
1781                 i = 0,
1782                 ln = array.length,
1783                 item;
1784
1785             for (; i < ln; i++) {
1786                 item = array[i];
1787
1788                 if (ExtArray.indexOf(clone, item) === -1) {
1789                     clone.push(item);
1790                 }
1791             }
1792
1793             return clone;
1794         },
1795
1796         /**
1797          * Creates a new array with all of the elements of this array for which
1798          * the provided filtering function returns true.
1799          *
1800          * @param {Array} array
1801          * @param {Function} fn Callback function for each item
1802          * @param {Object} scope Callback function scope
1803          * @return {Array} results
1804          */
1805         filter: function(array, fn, scope) {
1806             if (supportsFilter) {
1807                 return array.filter(fn, scope);
1808             }
1809
1810             var results = [],
1811                 i = 0,
1812                 ln = array.length;
1813
1814             for (; i < ln; i++) {
1815                 if (fn.call(scope, array[i], i, array)) {
1816                     results.push(array[i]);
1817                 }
1818             }
1819
1820             return results;
1821         },
1822
1823         /**
1824          * Converts a value to an array if it's not already an array; returns:
1825          *
1826          * - An empty array if given value is `undefined` or `null`
1827          * - Itself if given value is already an array
1828          * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
1829          * - An array with one item which is the given value, otherwise
1830          *
1831          * @param {Object} value The value to convert to an array if it's not already is an array
1832          * @param {Boolean} newReference (Optional) True to clone the given array and return a new reference if necessary,
1833          * defaults to false
1834          * @return {Array} array
1835          */
1836         from: function(value, newReference) {
1837             if (value === undefined || value === null) {
1838                 return [];
1839             }
1840
1841             if (Ext.isArray(value)) {
1842                 return (newReference) ? slice.call(value) : value;
1843             }
1844
1845             if (value && value.length !== undefined && typeof value !== 'string') {
1846                 return Ext.toArray(value);
1847             }
1848
1849             return [value];
1850         },
1851
1852         /**
1853          * Removes the specified item from the array if it exists
1854          *
1855          * @param {Array} array The array
1856          * @param {Object} item The item to remove
1857          * @return {Array} The passed array itself
1858          */
1859         remove: function(array, item) {
1860             var index = ExtArray.indexOf(array, item);
1861
1862             if (index !== -1) {
1863                 erase(array, index, 1);
1864             }
1865
1866             return array;
1867         },
1868
1869         /**
1870          * Push an item into the array only if the array doesn't contain it yet
1871          *
1872          * @param {Array} array The array
1873          * @param {Object} item The item to include
1874          */
1875         include: function(array, item) {
1876             if (!ExtArray.contains(array, item)) {
1877                 array.push(item);
1878             }
1879         },
1880
1881         /**
1882          * Clone a flat array without referencing the previous one. Note that this is different
1883          * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
1884          * for Array.prototype.slice.call(array)
1885          *
1886          * @param {Array} array The array
1887          * @return {Array} The clone array
1888          */
1889         clone: function(array) {
1890             return slice.call(array);
1891         },
1892
1893         /**
1894          * Merge multiple arrays into one with unique items.
1895          *
1896          * {@link Ext.Array#union} is alias for {@link Ext.Array#merge}
1897          *
1898          * @param {Array} array1
1899          * @param {Array} array2
1900          * @param {Array} etc
1901          * @return {Array} merged
1902          */
1903         merge: function() {
1904             var args = slice.call(arguments),
1905                 array = [],
1906                 i, ln;
1907
1908             for (i = 0, ln = args.length; i < ln; i++) {
1909                 array = array.concat(args[i]);
1910             }
1911
1912             return ExtArray.unique(array);
1913         },
1914
1915         /**
1916          * Merge multiple arrays into one with unique items that exist in all of the arrays.
1917          *
1918          * @param {Array} array1
1919          * @param {Array} array2
1920          * @param {Array} etc
1921          * @return {Array} intersect
1922          */
1923         intersect: function() {
1924             var intersect = [],
1925                 arrays = slice.call(arguments),
1926                 i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
1927
1928             if (!arrays.length) {
1929                 return intersect;
1930             }
1931
1932             // Find the smallest array
1933             for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
1934                 if (!minArray || array.length < minArray.length) {
1935                     minArray = array;
1936                     x = i;
1937                 }
1938             }
1939
1940             minArray = ExtArray.unique(minArray);
1941             erase(arrays, x, 1);
1942
1943             // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
1944             // an item in the small array, we're likely to find it before reaching the end
1945             // of the inner loop and can terminate the search early.
1946             for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
1947                 var count = 0;
1948
1949                 for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
1950                     for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
1951                         if (x === y) {
1952                             count++;
1953                             break;
1954                         }
1955                     }
1956                 }
1957
1958                 if (count === arraysLn) {
1959                     intersect.push(x);
1960                 }
1961             }
1962
1963             return intersect;
1964         },
1965
1966         /**
1967          * Perform a set difference A-B by subtracting all items in array B from array A.
1968          *
1969          * @param {Array} arrayA
1970          * @param {Array} arrayB
1971          * @return {Array} difference
1972          */
1973         difference: function(arrayA, arrayB) {
1974             var clone = slice.call(arrayA),
1975                 ln = clone.length,
1976                 i, j, lnB;
1977
1978             for (i = 0,lnB = arrayB.length; i < lnB; i++) {
1979                 for (j = 0; j < ln; j++) {
1980                     if (clone[j] === arrayB[i]) {
1981                         erase(clone, j, 1);
1982                         j--;
1983                         ln--;
1984                     }
1985                 }
1986             }
1987
1988             return clone;
1989         },
1990
1991         /**
1992          * Returns a shallow copy of a part of an array. This is equivalent to the native
1993          * call "Array.prototype.slice.call(array, begin, end)". This is often used when "array"
1994          * is "arguments" since the arguments object does not supply a slice method but can
1995          * be the context object to Array.prototype.slice.
1996          *
1997          * @param {Array} array The array (or arguments object).
1998          * @param {Number} begin The index at which to begin. Negative values are offsets from
1999          * the end of the array.
2000          * @param {Number} end The index at which to end. The copied items do not include
2001          * end. Negative values are offsets from the end of the array. If end is omitted,
2002          * all items up to the end of the array are copied.
2003          * @return {Array} The copied piece of the array.
2004          */
2005         // Note: IE6 will return [] on slice.call(x, undefined).
2006         slice: ([1,2].slice(1, undefined).length ?
2007             function (array, begin, end) {
2008                 return slice.call(array, begin, end);
2009             } :
2010             // at least IE6 uses arguments.length for variadic signature
2011             function (array, begin, end) {
2012                 // After tested for IE 6, the one below is of the best performance
2013                 // see http://jsperf.com/slice-fix
2014                 if (typeof begin === 'undefined') {
2015                     return slice.call(array);
2016                 }
2017                 if (typeof end === 'undefined') {
2018                     return slice.call(array, begin);
2019                 }
2020                 return slice.call(array, begin, end);
2021             }
2022         ),
2023
2024         /**
2025          * Sorts the elements of an Array.
2026          * By default, this method sorts the elements alphabetically and ascending.
2027          *
2028          * @param {Array} array The array to sort.
2029          * @param {Function} sortFn (optional) The comparison function.
2030          * @return {Array} The sorted array.
2031          */
2032         sort: function(array, sortFn) {
2033             if (supportsSort) {
2034                 if (sortFn) {
2035                     return array.sort(sortFn);
2036                 } else {
2037                     return array.sort();
2038                 }
2039             }
2040
2041             var length = array.length,
2042                 i = 0,
2043                 comparison,
2044                 j, min, tmp;
2045
2046             for (; i < length; i++) {
2047                 min = i;
2048                 for (j = i + 1; j < length; j++) {
2049                     if (sortFn) {
2050                         comparison = sortFn(array[j], array[min]);
2051                         if (comparison < 0) {
2052                             min = j;
2053                         }
2054                     } else if (array[j] < array[min]) {
2055                         min = j;
2056                     }
2057                 }
2058                 if (min !== i) {
2059                     tmp = array[i];
2060                     array[i] = array[min];
2061                     array[min] = tmp;
2062                 }
2063             }
2064
2065             return array;
2066         },
2067
2068         /**
2069          * Recursively flattens into 1-d Array. Injects Arrays inline.
2070          *
2071          * @param {Array} array The array to flatten
2072          * @return {Array} The 1-d array.
2073          */
2074         flatten: function(array) {
2075             var worker = [];
2076
2077             function rFlatten(a) {
2078                 var i, ln, v;
2079
2080                 for (i = 0, ln = a.length; i < ln; i++) {
2081                     v = a[i];
2082
2083                     if (Ext.isArray(v)) {
2084                         rFlatten(v);
2085                     } else {
2086                         worker.push(v);
2087                     }
2088                 }
2089
2090                 return worker;
2091             }
2092
2093             return rFlatten(array);
2094         },
2095
2096         /**
2097          * Returns the minimum value in the Array.
2098          *
2099          * @param {Array/NodeList} array The Array from which to select the minimum value.
2100          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
2101          * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
2102          * @return {Object} minValue The minimum value
2103          */
2104         min: function(array, comparisonFn) {
2105             var min = array[0],
2106                 i, ln, item;
2107
2108             for (i = 0, ln = array.length; i < ln; i++) {
2109                 item = array[i];
2110
2111                 if (comparisonFn) {
2112                     if (comparisonFn(min, item) === 1) {
2113                         min = item;
2114                     }
2115                 }
2116                 else {
2117                     if (item < min) {
2118                         min = item;
2119                     }
2120                 }
2121             }
2122
2123             return min;
2124         },
2125
2126         /**
2127          * Returns the maximum value in the Array.
2128          *
2129          * @param {Array/NodeList} array The Array from which to select the maximum value.
2130          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
2131          * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
2132          * @return {Object} maxValue The maximum value
2133          */
2134         max: function(array, comparisonFn) {
2135             var max = array[0],
2136                 i, ln, item;
2137
2138             for (i = 0, ln = array.length; i < ln; i++) {
2139                 item = array[i];
2140
2141                 if (comparisonFn) {
2142                     if (comparisonFn(max, item) === -1) {
2143                         max = item;
2144                     }
2145                 }
2146                 else {
2147                     if (item > max) {
2148                         max = item;
2149                     }
2150                 }
2151             }
2152
2153             return max;
2154         },
2155
2156         /**
2157          * Calculates the mean of all items in the array.
2158          *
2159          * @param {Array} array The Array to calculate the mean value of.
2160          * @return {Number} The mean.
2161          */
2162         mean: function(array) {
2163             return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
2164         },
2165
2166         /**
2167          * Calculates the sum of all items in the given array.
2168          *
2169          * @param {Array} array The Array to calculate the sum value of.
2170          * @return {Number} The sum.
2171          */
2172         sum: function(array) {
2173             var sum = 0,
2174                 i, ln, item;
2175
2176             for (i = 0,ln = array.length; i < ln; i++) {
2177                 item = array[i];
2178
2179                 sum += item;
2180             }
2181
2182             return sum;
2183         },
2184
2185         _replaceSim: replaceSim, // for unit testing
2186         _spliceSim: spliceSim,
2187
2188         /**
2189          * Removes items from an array. This is functionally equivalent to the splice method
2190          * of Array, but works around bugs in IE8's splice method and does not copy the
2191          * removed elements in order to return them (because very often they are ignored).
2192          *
2193          * @param {Array} array The Array on which to replace.
2194          * @param {Number} index The index in the array at which to operate.
2195          * @param {Number} removeCount The number of items to remove at index.
2196          * @return {Array} The array passed.
2197          * @method
2198          */
2199         erase: erase,
2200
2201         /**
2202          * Inserts items in to an array.
2203          *
2204          * @param {Array} array The Array on which to replace.
2205          * @param {Number} index The index in the array at which to operate.
2206          * @param {Array} items The array of items to insert at index.
2207          * @return {Array} The array passed.
2208          */
2209         insert: function (array, index, items) {
2210             return replace(array, index, 0, items);
2211         },
2212
2213         /**
2214          * Replaces items in an array. This is functionally equivalent to the splice method
2215          * of Array, but works around bugs in IE8's splice method and is often more convenient
2216          * to call because it accepts an array of items to insert rather than use a variadic
2217          * argument list.
2218          *
2219          * @param {Array} array The Array on which to replace.
2220          * @param {Number} index The index in the array at which to operate.
2221          * @param {Number} removeCount The number of items to remove at index (can be 0).
2222          * @param {Array} insert (optional) An array of items to insert at index.
2223          * @return {Array} The array passed.
2224          * @method
2225          */
2226         replace: replace,
2227
2228         /**
2229          * Replaces items in an array. This is equivalent to the splice method of Array, but
2230          * works around bugs in IE8's splice method. The signature is exactly the same as the
2231          * splice method except that the array is the first argument. All arguments following
2232          * removeCount are inserted in the array at index.
2233          *
2234          * @param {Array} array The Array on which to replace.
2235          * @param {Number} index The index in the array at which to operate.
2236          * @param {Number} removeCount The number of items to remove at index (can be 0).
2237          * @return {Array} An array containing the removed items.
2238          * @method
2239          */
2240         splice: splice
2241     };
2242
2243     /**
2244      * @method
2245      * @member Ext
2246      * @alias Ext.Array#each
2247      */
2248     Ext.each = ExtArray.each;
2249
2250     /**
2251      * @method
2252      * @member Ext.Array
2253      * @alias Ext.Array#merge
2254      */
2255     ExtArray.union = ExtArray.merge;
2256
2257     /**
2258      * Old alias to {@link Ext.Array#min}
2259      * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
2260      * @method
2261      * @member Ext
2262      * @alias Ext.Array#min
2263      */
2264     Ext.min = ExtArray.min;
2265
2266     /**
2267      * Old alias to {@link Ext.Array#max}
2268      * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
2269      * @method
2270      * @member Ext
2271      * @alias Ext.Array#max
2272      */
2273     Ext.max = ExtArray.max;
2274
2275     /**
2276      * Old alias to {@link Ext.Array#sum}
2277      * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
2278      * @method
2279      * @member Ext
2280      * @alias Ext.Array#sum
2281      */
2282     Ext.sum = ExtArray.sum;
2283
2284     /**
2285      * Old alias to {@link Ext.Array#mean}
2286      * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
2287      * @method
2288      * @member Ext
2289      * @alias Ext.Array#mean
2290      */
2291     Ext.mean = ExtArray.mean;
2292
2293     /**
2294      * Old alias to {@link Ext.Array#flatten}
2295      * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
2296      * @method
2297      * @member Ext
2298      * @alias Ext.Array#flatten
2299      */
2300     Ext.flatten = ExtArray.flatten;
2301
2302     /**
2303      * Old alias to {@link Ext.Array#clean}
2304      * @deprecated 4.0.0 Use {@link Ext.Array#clean} instead
2305      * @method
2306      * @member Ext
2307      * @alias Ext.Array#clean
2308      */
2309     Ext.clean = ExtArray.clean;
2310
2311     /**
2312      * Old alias to {@link Ext.Array#unique}
2313      * @deprecated 4.0.0 Use {@link Ext.Array#unique} instead
2314      * @method
2315      * @member Ext
2316      * @alias Ext.Array#unique
2317      */
2318     Ext.unique = ExtArray.unique;
2319
2320     /**
2321      * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
2322      * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
2323      * @method
2324      * @member Ext
2325      * @alias Ext.Array#pluck
2326      */
2327     Ext.pluck = ExtArray.pluck;
2328
2329     /**
2330      * @method
2331      * @member Ext
2332      * @alias Ext.Array#toArray
2333      */
2334     Ext.toArray = function() {
2335         return ExtArray.toArray.apply(ExtArray, arguments);
2336     };
2337 })();
2338
2339 /**
2340  * @class Ext.Function
2341  *
2342  * A collection of useful static methods to deal with function callbacks
2343  * @singleton
2344  */
2345 Ext.Function = {
2346
2347     /**
2348      * A very commonly used method throughout the framework. It acts as a wrapper around another method
2349      * which originally accepts 2 arguments for `name` and `value`.
2350      * The wrapped function then allows "flexible" value setting of either:
2351      *
2352      * - `name` and `value` as 2 arguments
2353      * - one single object argument with multiple key - value pairs
2354      *
2355      * For example:
2356      *
2357      *     var setValue = Ext.Function.flexSetter(function(name, value) {
2358      *         this[name] = value;
2359      *     });
2360      *
2361      *     // Afterwards
2362      *     // Setting a single name - value
2363      *     setValue('name1', 'value1');
2364      *
2365      *     // Settings multiple name - value pairs
2366      *     setValue({
2367      *         name1: 'value1',
2368      *         name2: 'value2',
2369      *         name3: 'value3'
2370      *     });
2371      *
2372      * @param {Function} setter
2373      * @returns {Function} flexSetter
2374      */
2375     flexSetter: function(fn) {
2376         return function(a, b) {
2377             var k, i;
2378
2379             if (a === null) {
2380                 return this;
2381             }
2382
2383             if (typeof a !== 'string') {
2384                 for (k in a) {
2385                     if (a.hasOwnProperty(k)) {
2386                         fn.call(this, k, a[k]);
2387                     }
2388                 }
2389
2390                 if (Ext.enumerables) {
2391                     for (i = Ext.enumerables.length; i--;) {
2392                         k = Ext.enumerables[i];
2393                         if (a.hasOwnProperty(k)) {
2394                             fn.call(this, k, a[k]);
2395                         }
2396                     }
2397                 }
2398             } else {
2399                 fn.call(this, a, b);
2400             }
2401
2402             return this;
2403         };
2404     },
2405
2406     /**
2407      * Create a new function from the provided `fn`, change `this` to the provided scope, optionally
2408      * overrides arguments for the call. (Defaults to the arguments passed by the caller)
2409      *
2410      * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}
2411      *
2412      * @param {Function} fn The function to delegate.
2413      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2414      * **If omitted, defaults to the browser window.**
2415      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2416      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2417      * if a number the args are inserted at the specified position
2418      * @return {Function} The new function
2419      */
2420     bind: function(fn, scope, args, appendArgs) {
2421         if (arguments.length === 2) {
2422             return function() {
2423                 return fn.apply(scope, arguments);
2424             }
2425         }
2426
2427         var method = fn,
2428             slice = Array.prototype.slice;
2429
2430         return function() {
2431             var callArgs = args || arguments;
2432
2433             if (appendArgs === true) {
2434                 callArgs = slice.call(arguments, 0);
2435                 callArgs = callArgs.concat(args);
2436             }
2437             else if (typeof appendArgs == 'number') {
2438                 callArgs = slice.call(arguments, 0); // copy arguments first
2439                 Ext.Array.insert(callArgs, appendArgs, args);
2440             }
2441
2442             return method.apply(scope || window, callArgs);
2443         };
2444     },
2445
2446     /**
2447      * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
2448      * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
2449      * This is especially useful when creating callbacks.
2450      *
2451      * For example:
2452      *
2453      *     var originalFunction = function(){
2454      *         alert(Ext.Array.from(arguments).join(' '));
2455      *     };
2456      *
2457      *     var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
2458      *
2459      *     callback(); // alerts 'Hello World'
2460      *     callback('by Me'); // alerts 'Hello World by Me'
2461      *
2462      * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
2463      *
2464      * @param {Function} fn The original function
2465      * @param {Array} args The arguments to pass to new callback
2466      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2467      * @return {Function} The new callback function
2468      */
2469     pass: function(fn, args, scope) {
2470         if (args) {
2471             args = Ext.Array.from(args);
2472         }
2473
2474         return function() {
2475             return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
2476         };
2477     },
2478
2479     /**
2480      * Create an alias to the provided method property with name `methodName` of `object`.
2481      * Note that the execution scope will still be bound to the provided `object` itself.
2482      *
2483      * @param {Object/Function} object
2484      * @param {String} methodName
2485      * @return {Function} aliasFn
2486      */
2487     alias: function(object, methodName) {
2488         return function() {
2489             return object[methodName].apply(object, arguments);
2490         };
2491     },
2492
2493     /**
2494      * Creates an interceptor function. The passed function is called before the original one. If it returns false,
2495      * the original one is not called. The resulting function returns the results of the original function.
2496      * The passed function is called with the parameters of the original function. Example usage:
2497      *
2498      *     var sayHi = function(name){
2499      *         alert('Hi, ' + name);
2500      *     }
2501      *
2502      *     sayHi('Fred'); // alerts "Hi, Fred"
2503      *
2504      *     // create a new function that validates input without
2505      *     // directly modifying the original function:
2506      *     var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
2507      *         return name == 'Brian';
2508      *     });
2509      *
2510      *     sayHiToFriend('Fred');  // no alert
2511      *     sayHiToFriend('Brian'); // alerts "Hi, Brian"
2512      *
2513      * @param {Function} origFn The original function.
2514      * @param {Function} newFn The function to call before the original
2515      * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
2516      * **If omitted, defaults to the scope in which the original function is called or the browser window.**
2517      * @param {Object} returnValue (optional) The value to return if the passed function return false (defaults to null).
2518      * @return {Function} The new function
2519      */
2520     createInterceptor: function(origFn, newFn, scope, returnValue) {
2521         var method = origFn;
2522         if (!Ext.isFunction(newFn)) {
2523             return origFn;
2524         }
2525         else {
2526             return function() {
2527                 var me = this,
2528                     args = arguments;
2529                 newFn.target = me;
2530                 newFn.method = origFn;
2531                 return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
2532             };
2533         }
2534     },
2535
2536     /**
2537      * Creates a delegate (callback) which, when called, executes after a specific delay.
2538      *
2539      * @param {Function} fn The function which will be called on a delay when the returned function is called.
2540      * Optionally, a replacement (or additional) argument list may be specified.
2541      * @param {Number} delay The number of milliseconds to defer execution by whenever called.
2542      * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
2543      * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
2544      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2545      * if a number the args are inserted at the specified position.
2546      * @return {Function} A function which, when called, executes the original function after the specified delay.
2547      */
2548     createDelayed: function(fn, delay, scope, args, appendArgs) {
2549         if (scope || args) {
2550             fn = Ext.Function.bind(fn, scope, args, appendArgs);
2551         }
2552         return function() {
2553             var me = this;
2554             setTimeout(function() {
2555                 fn.apply(me, arguments);
2556             }, delay);
2557         };
2558     },
2559
2560     /**
2561      * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
2562      *
2563      *     var sayHi = function(name){
2564      *         alert('Hi, ' + name);
2565      *     }
2566      *
2567      *     // executes immediately:
2568      *     sayHi('Fred');
2569      *
2570      *     // executes after 2 seconds:
2571      *     Ext.Function.defer(sayHi, 2000, this, ['Fred']);
2572      *
2573      *     // this syntax is sometimes useful for deferring
2574      *     // execution of an anonymous function:
2575      *     Ext.Function.defer(function(){
2576      *         alert('Anonymous');
2577      *     }, 100);
2578      *
2579      * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}
2580      *
2581      * @param {Function} fn The function to defer.
2582      * @param {Number} millis The number of milliseconds for the setTimeout call
2583      * (if less than or equal to 0 the function is executed immediately)
2584      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2585      * **If omitted, defaults to the browser window.**
2586      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2587      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2588      * if a number the args are inserted at the specified position
2589      * @return {Number} The timeout id that can be used with clearTimeout
2590      */
2591     defer: function(fn, millis, obj, args, appendArgs) {
2592         fn = Ext.Function.bind(fn, obj, args, appendArgs);
2593         if (millis > 0) {
2594             return setTimeout(fn, millis);
2595         }
2596         fn();
2597         return 0;
2598     },
2599
2600     /**
2601      * Create a combined function call sequence of the original function + the passed function.
2602      * The resulting function returns the results of the original function.
2603      * The passed function is called with the parameters of the original function. Example usage:
2604      *
2605      *     var sayHi = function(name){
2606      *         alert('Hi, ' + name);
2607      *     }
2608      *
2609      *     sayHi('Fred'); // alerts "Hi, Fred"
2610      *
2611      *     var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
2612      *         alert('Bye, ' + name);
2613      *     });
2614      *
2615      *     sayGoodbye('Fred'); // both alerts show
2616      *
2617      * @param {Function} origFn The original function.
2618      * @param {Function} newFn The function to sequence
2619      * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
2620      * If omitted, defaults to the scope in which the original function is called or the browser window.
2621      * @return {Function} The new function
2622      */
2623     createSequence: function(origFn, newFn, scope) {
2624         if (!Ext.isFunction(newFn)) {
2625             return origFn;
2626         }
2627         else {
2628             return function() {
2629                 var retval = origFn.apply(this || window, arguments);
2630                 newFn.apply(scope || this || window, arguments);
2631                 return retval;
2632             };
2633         }
2634     },
2635
2636     /**
2637      * Creates a delegate function, optionally with a bound scope which, when called, buffers
2638      * the execution of the passed function for the configured number of milliseconds.
2639      * If called again within that period, the impending invocation will be canceled, and the
2640      * timeout period will begin again.
2641      *
2642      * @param {Function} fn The function to invoke on a buffered timer.
2643      * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
2644      * function.
2645      * @param {Object} scope (optional) The scope (`this` reference) in which
2646      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2647      * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
2648      * passed by the caller.
2649      * @return {Function} A function which invokes the passed function after buffering for the specified time.
2650      */
2651     createBuffered: function(fn, buffer, scope, args) {
2652         return function(){
2653             var timerId;
2654             return function() {
2655                 var me = this;
2656                 if (timerId) {
2657                     clearTimeout(timerId);
2658                     timerId = null;
2659                 }
2660                 timerId = setTimeout(function(){
2661                     fn.apply(scope || me, args || arguments);
2662                 }, buffer);
2663             };
2664         }();
2665     },
2666
2667     /**
2668      * Creates a throttled version of the passed function which, when called repeatedly and
2669      * rapidly, invokes the passed function only after a certain interval has elapsed since the
2670      * previous invocation.
2671      *
2672      * This is useful for wrapping functions which may be called repeatedly, such as
2673      * a handler of a mouse move event when the processing is expensive.
2674      *
2675      * @param {Function} fn The function to execute at a regular time interval.
2676      * @param {Number} interval The interval **in milliseconds** on which the passed function is executed.
2677      * @param {Object} scope (optional) The scope (`this` reference) in which
2678      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2679      * @returns {Function} A function which invokes the passed function at the specified interval.
2680      */
2681     createThrottled: function(fn, interval, scope) {
2682         var lastCallTime, elapsed, lastArgs, timer, execute = function() {
2683             fn.apply(scope || this, lastArgs);
2684             lastCallTime = new Date().getTime();
2685         };
2686
2687         return function() {
2688             elapsed = new Date().getTime() - lastCallTime;
2689             lastArgs = arguments;
2690
2691             clearTimeout(timer);
2692             if (!lastCallTime || (elapsed >= interval)) {
2693                 execute();
2694             } else {
2695                 timer = setTimeout(execute, interval - elapsed);
2696             }
2697         };
2698     },
2699
2700     /**
2701      * Adds behavior to an existing method that is executed before the
2702      * original behavior of the function.  For example:
2703      * 
2704      *     var soup = {
2705      *         contents: [],
2706      *         add: function(ingredient) {
2707      *             this.contents.push(ingredient);
2708      *         }
2709      *     };
2710      *     Ext.Function.interceptBefore(soup, "add", function(ingredient){
2711      *         if (!this.contents.length && ingredient !== "water") {
2712      *             // Always add water to start with
2713      *             this.contents.push("water");
2714      *         }
2715      *     });
2716      *     soup.add("onions");
2717      *     soup.add("salt");
2718      *     soup.contents; // will contain: water, onions, salt
2719      * 
2720      * @param {Object} object The target object
2721      * @param {String} methodName Name of the method to override
2722      * @param {Function} fn Function with the new behavior.  It will
2723      * be called with the same arguments as the original method.  The
2724      * return value of this function will be the return value of the
2725      * new method.
2726      * @return {Function} The new function just created.
2727      */
2728     interceptBefore: function(object, methodName, fn) {
2729         var method = object[methodName] || Ext.emptyFn;
2730
2731         return object[methodName] = function() {
2732             var ret = fn.apply(this, arguments);
2733             method.apply(this, arguments);
2734
2735             return ret;
2736         };
2737     },
2738
2739     /**
2740      * Adds behavior to an existing method that is executed after the
2741      * original behavior of the function.  For example:
2742      * 
2743      *     var soup = {
2744      *         contents: [],
2745      *         add: function(ingredient) {
2746      *             this.contents.push(ingredient);
2747      *         }
2748      *     };
2749      *     Ext.Function.interceptAfter(soup, "add", function(ingredient){
2750      *         // Always add a bit of extra salt
2751      *         this.contents.push("salt");
2752      *     });
2753      *     soup.add("water");
2754      *     soup.add("onions");
2755      *     soup.contents; // will contain: water, salt, onions, salt
2756      * 
2757      * @param {Object} object The target object
2758      * @param {String} methodName Name of the method to override
2759      * @param {Function} fn Function with the new behavior.  It will
2760      * be called with the same arguments as the original method.  The
2761      * return value of this function will be the return value of the
2762      * new method.
2763      * @return {Function} The new function just created.
2764      */
2765     interceptAfter: function(object, methodName, fn) {
2766         var method = object[methodName] || Ext.emptyFn;
2767
2768         return object[methodName] = function() {
2769             method.apply(this, arguments);
2770             return fn.apply(this, arguments);
2771         };
2772     }
2773 };
2774
2775 /**
2776  * @method
2777  * @member Ext
2778  * @alias Ext.Function#defer
2779  */
2780 Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
2781
2782 /**
2783  * @method
2784  * @member Ext
2785  * @alias Ext.Function#pass
2786  */
2787 Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
2788
2789 /**
2790  * @method
2791  * @member Ext
2792  * @alias Ext.Function#bind
2793  */
2794 Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
2795
2796 /**
2797  * @author Jacky Nguyen <jacky@sencha.com>
2798  * @docauthor Jacky Nguyen <jacky@sencha.com>
2799  * @class Ext.Object
2800  *
2801  * A collection of useful static methods to deal with objects.
2802  *
2803  * @singleton
2804  */
2805
2806 (function() {
2807
2808 var ExtObject = Ext.Object = {
2809
2810     /**
2811      * Converts a `name` - `value` pair to an array of objects with support for nested structures. Useful to construct
2812      * query strings. For example:
2813      *
2814      *     var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
2815      *
2816      *     // objects then equals:
2817      *     [
2818      *         { name: 'hobbies', value: 'reading' },
2819      *         { name: 'hobbies', value: 'cooking' },
2820      *         { name: 'hobbies', value: 'swimming' },
2821      *     ];
2822      *
2823      *     var objects = Ext.Object.toQueryObjects('dateOfBirth', {
2824      *         day: 3,
2825      *         month: 8,
2826      *         year: 1987,
2827      *         extra: {
2828      *             hour: 4
2829      *             minute: 30
2830      *         }
2831      *     }, true); // Recursive
2832      *
2833      *     // objects then equals:
2834      *     [
2835      *         { name: 'dateOfBirth[day]', value: 3 },
2836      *         { name: 'dateOfBirth[month]', value: 8 },
2837      *         { name: 'dateOfBirth[year]', value: 1987 },
2838      *         { name: 'dateOfBirth[extra][hour]', value: 4 },
2839      *         { name: 'dateOfBirth[extra][minute]', value: 30 },
2840      *     ];
2841      *
2842      * @param {String} name
2843      * @param {Object/Array} value
2844      * @param {Boolean} [recursive=false] True to traverse object recursively
2845      * @return {Array}
2846      */
2847     toQueryObjects: function(name, value, recursive) {
2848         var self = ExtObject.toQueryObjects,
2849             objects = [],
2850             i, ln;
2851
2852         if (Ext.isArray(value)) {
2853             for (i = 0, ln = value.length; i < ln; i++) {
2854                 if (recursive) {
2855                     objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2856                 }
2857                 else {
2858                     objects.push({
2859                         name: name,
2860                         value: value[i]
2861                     });
2862                 }
2863             }
2864         }
2865         else if (Ext.isObject(value)) {
2866             for (i in value) {
2867                 if (value.hasOwnProperty(i)) {
2868                     if (recursive) {
2869                         objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2870                     }
2871                     else {
2872                         objects.push({
2873                             name: name,
2874                             value: value[i]
2875                         });
2876                     }
2877                 }
2878             }
2879         }
2880         else {
2881             objects.push({
2882                 name: name,
2883                 value: value
2884             });
2885         }
2886
2887         return objects;
2888     },
2889
2890     /**
2891      * Takes an object and converts it to an encoded query string.
2892      *
2893      * Non-recursive:
2894      *
2895      *     Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
2896      *     Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
2897      *     Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
2898      *     Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
2899      *     Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
2900      *
2901      * Recursive:
2902      *
2903      *     Ext.Object.toQueryString({
2904      *         username: 'Jacky',
2905      *         dateOfBirth: {
2906      *             day: 1,
2907      *             month: 2,
2908      *             year: 1911
2909      *         },
2910      *         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2911      *     }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
2912      *     // username=Jacky
2913      *     //    &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
2914      *     //    &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
2915      *
2916      * @param {Object} object The object to encode
2917      * @param {Boolean} [recursive=false] Whether or not to interpret the object in recursive format.
2918      * (PHP / Ruby on Rails servers and similar).
2919      * @return {String} queryString
2920      */
2921     toQueryString: function(object, recursive) {
2922         var paramObjects = [],
2923             params = [],
2924             i, j, ln, paramObject, value;
2925
2926         for (i in object) {
2927             if (object.hasOwnProperty(i)) {
2928                 paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
2929             }
2930         }
2931
2932         for (j = 0, ln = paramObjects.length; j < ln; j++) {
2933             paramObject = paramObjects[j];
2934             value = paramObject.value;
2935
2936             if (Ext.isEmpty(value)) {
2937                 value = '';
2938             }
2939             else if (Ext.isDate(value)) {
2940                 value = Ext.Date.toString(value);
2941             }
2942
2943             params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
2944         }
2945
2946         return params.join('&');
2947     },
2948
2949     /**
2950      * Converts a query string back into an object.
2951      *
2952      * Non-recursive:
2953      *
2954      *     Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
2955      *     Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
2956      *     Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
2957      *     Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
2958      *
2959      * Recursive:
2960      *
2961      *       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);
2962      *     // returns
2963      *     {
2964      *         username: 'Jacky',
2965      *         dateOfBirth: {
2966      *             day: '1',
2967      *             month: '2',
2968      *             year: '1911'
2969      *         },
2970      *         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2971      *     }
2972      *
2973      * @param {String} queryString The query string to decode
2974      * @param {Boolean} [recursive=false] Whether or not to recursively decode the string. This format is supported by
2975      * PHP / Ruby on Rails servers and similar.
2976      * @return {Object}
2977      */
2978     fromQueryString: function(queryString, recursive) {
2979         var parts = queryString.replace(/^\?/, '').split('&'),
2980             object = {},
2981             temp, components, name, value, i, ln,
2982             part, j, subLn, matchedKeys, matchedName,
2983             keys, key, nextKey;
2984
2985         for (i = 0, ln = parts.length; i < ln; i++) {
2986             part = parts[i];
2987
2988             if (part.length > 0) {
2989                 components = part.split('=');
2990                 name = decodeURIComponent(components[0]);
2991                 value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
2992
2993                 if (!recursive) {
2994                     if (object.hasOwnProperty(name)) {
2995                         if (!Ext.isArray(object[name])) {
2996                             object[name] = [object[name]];
2997                         }
2998
2999                         object[name].push(value);
3000                     }
3001                     else {
3002                         object[name] = value;
3003                     }
3004                 }
3005                 else {
3006                     matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
3007                     matchedName = name.match(/^([^\[]+)/);
3008
3009                     if (!matchedName) {
3010                         Ext.Error.raise({
3011                             sourceClass: "Ext.Object",
3012                             sourceMethod: "fromQueryString",
3013                             queryString: queryString,
3014                             recursive: recursive,
3015                             msg: 'Malformed query string given, failed parsing name from "' + part + '"'
3016                         });
3017                     }
3018
3019                     name = matchedName[0];
3020                     keys = [];
3021
3022                     if (matchedKeys === null) {
3023                         object[name] = value;
3024                         continue;
3025                     }
3026
3027                     for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
3028                         key = matchedKeys[j];
3029                         key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
3030                         keys.push(key);
3031                     }
3032
3033                     keys.unshift(name);
3034
3035                     temp = object;
3036
3037                     for (j = 0, subLn = keys.length; j < subLn; j++) {
3038                         key = keys[j];
3039
3040                         if (j === subLn - 1) {
3041                             if (Ext.isArray(temp) && key === '') {
3042                                 temp.push(value);
3043                             }
3044                             else {
3045                                 temp[key] = value;
3046                             }
3047                         }
3048                         else {
3049                             if (temp[key] === undefined || typeof temp[key] === 'string') {
3050                                 nextKey = keys[j+1];
3051
3052                                 temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
3053                             }
3054
3055                             temp = temp[key];
3056                         }
3057                     }
3058                 }
3059             }
3060         }
3061
3062         return object;
3063     },
3064
3065     /**
3066      * Iterates through an object and invokes the given callback function for each iteration.
3067      * The iteration can be stopped by returning `false` in the callback function. For example:
3068      *
3069      *     var person = {
3070      *         name: 'Jacky'
3071      *         hairColor: 'black'
3072      *         loves: ['food', 'sleeping', 'wife']
3073      *     };
3074      *
3075      *     Ext.Object.each(person, function(key, value, myself) {
3076      *         console.log(key + ":" + value);
3077      *
3078      *         if (key === 'hairColor') {
3079      *             return false; // stop the iteration
3080      *         }
3081      *     });
3082      *
3083      * @param {Object} object The object to iterate
3084      * @param {Function} fn The callback function.
3085      * @param {String} fn.key
3086      * @param {Object} fn.value
3087      * @param {Object} fn.object The object itself
3088      * @param {Object} [scope] The execution scope (`this`) of the callback function
3089      */
3090     each: function(object, fn, scope) {
3091         for (var property in object) {
3092             if (object.hasOwnProperty(property)) {
3093                 if (fn.call(scope || object, property, object[property], object) === false) {
3094                     return;
3095                 }
3096             }
3097         }
3098     },
3099
3100     /**
3101      * Merges any number of objects recursively without referencing them or their children.
3102      *
3103      *     var extjs = {
3104      *         companyName: 'Ext JS',
3105      *         products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
3106      *         isSuperCool: true
3107      *         office: {
3108      *             size: 2000,
3109      *             location: 'Palo Alto',
3110      *             isFun: true
3111      *         }
3112      *     };
3113      *
3114      *     var newStuff = {
3115      *         companyName: 'Sencha Inc.',
3116      *         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
3117      *         office: {
3118      *             size: 40000,
3119      *             location: 'Redwood City'
3120      *         }
3121      *     };
3122      *
3123      *     var sencha = Ext.Object.merge(extjs, newStuff);
3124      *
3125      *     // extjs and sencha then equals to
3126      *     {
3127      *         companyName: 'Sencha Inc.',
3128      *         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
3129      *         isSuperCool: true
3130      *         office: {
3131      *             size: 30000,
3132      *             location: 'Redwood City'
3133      *             isFun: true
3134      *         }
3135      *     }
3136      *
3137      * @param {Object...} object Any number of objects to merge.
3138      * @return {Object} merged The object that is created as a result of merging all the objects passed in.
3139      */
3140     merge: function(source, key, value) {
3141         if (typeof key === 'string') {
3142             if (value && value.constructor === Object) {
3143                 if (source[key] && source[key].constructor === Object) {
3144                     ExtObject.merge(source[key], value);
3145                 }
3146                 else {
3147                     source[key] = Ext.clone(value);
3148                 }
3149             }
3150             else {
3151                 source[key] = value;
3152             }
3153
3154             return source;
3155         }
3156
3157         var i = 1,
3158             ln = arguments.length,
3159             object, property;
3160
3161         for (; i < ln; i++) {
3162             object = arguments[i];
3163
3164             for (property in object) {
3165                 if (object.hasOwnProperty(property)) {
3166                     ExtObject.merge(source, property, object[property]);
3167                 }
3168             }
3169         }
3170
3171         return source;
3172     },
3173
3174     /**
3175      * Returns the first matching key corresponding to the given value.
3176      * If no matching value is found, null is returned.
3177      *
3178      *     var person = {
3179      *         name: 'Jacky',
3180      *         loves: 'food'
3181      *     };
3182      *
3183      *     alert(Ext.Object.getKey(person, 'food')); // alerts 'loves'
3184      *
3185      * @param {Object} object
3186      * @param {Object} value The value to find
3187      */
3188     getKey: function(object, value) {
3189         for (var property in object) {
3190             if (object.hasOwnProperty(property) && object[property] === value) {
3191                 return property;
3192             }
3193         }
3194
3195         return null;
3196     },
3197
3198     /**
3199      * Gets all values of the given object as an array.
3200      *
3201      *     var values = Ext.Object.getValues({
3202      *         name: 'Jacky',
3203      *         loves: 'food'
3204      *     }); // ['Jacky', 'food']
3205      *
3206      * @param {Object} object
3207      * @return {Array} An array of values from the object
3208      */
3209     getValues: function(object) {
3210         var values = [],
3211             property;
3212
3213         for (property in object) {
3214             if (object.hasOwnProperty(property)) {
3215                 values.push(object[property]);
3216             }
3217         }
3218
3219         return values;
3220     },
3221
3222     /**
3223      * Gets all keys of the given object as an array.
3224      *
3225      *     var values = Ext.Object.getKeys({
3226      *         name: 'Jacky',
3227      *         loves: 'food'
3228      *     }); // ['name', 'loves']
3229      *
3230      * @param {Object} object
3231      * @return {String[]} An array of keys from the object
3232      * @method
3233      */
3234     getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
3235         var keys = [],
3236             property;
3237
3238         for (property in object) {
3239             if (object.hasOwnProperty(property)) {
3240                 keys.push(property);
3241             }
3242         }
3243
3244         return keys;
3245     },
3246
3247     /**
3248      * Gets the total number of this object's own properties
3249      *
3250      *     var size = Ext.Object.getSize({
3251      *         name: 'Jacky',
3252      *         loves: 'food'
3253      *     }); // size equals 2
3254      *
3255      * @param {Object} object
3256      * @return {Number} size
3257      */
3258     getSize: function(object) {
3259         var size = 0,
3260             property;
3261
3262         for (property in object) {
3263             if (object.hasOwnProperty(property)) {
3264                 size++;
3265             }
3266         }
3267
3268         return size;
3269     }
3270 };
3271
3272
3273 /**
3274  * A convenient alias method for {@link Ext.Object#merge}.
3275  *
3276  * @member Ext
3277  * @method merge
3278  * @alias Ext.Object#merge
3279  */
3280 Ext.merge = Ext.Object.merge;
3281
3282 /**
3283  * Alias for {@link Ext.Object#toQueryString}.
3284  *
3285  * @member Ext
3286  * @method urlEncode
3287  * @alias Ext.Object#toQueryString
3288  * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString} instead
3289  */
3290 Ext.urlEncode = function() {
3291     var args = Ext.Array.from(arguments),
3292         prefix = '';
3293
3294     // Support for the old `pre` argument
3295     if ((typeof args[1] === 'string')) {
3296         prefix = args[1] + '&';
3297         args[1] = false;
3298     }
3299
3300     return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
3301 };
3302
3303 /**
3304  * Alias for {@link Ext.Object#fromQueryString}.
3305  *
3306  * @member Ext
3307  * @method urlDecode
3308  * @alias Ext.Object#fromQueryString
3309  * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString} instead
3310  */
3311 Ext.urlDecode = function() {
3312     return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
3313 };
3314
3315 })();
3316
3317 /**
3318  * @class Ext.Date
3319  * A set of useful static methods to deal with date
3320  * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
3321  * this object for convenience
3322  *
3323  * The date parsing and formatting syntax contains a subset of
3324  * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
3325  * supported will provide results equivalent to their PHP versions.
3326  *
3327  * The following is a list of all currently supported formats:
3328  * <pre class="">
3329 Format  Description                                                               Example returned values
3330 ------  -----------------------------------------------------------------------   -----------------------
3331   d     Day of the month, 2 digits with leading zeros                             01 to 31
3332   D     A short textual representation of the day of the week                     Mon to Sun
3333   j     Day of the month without leading zeros                                    1 to 31
3334   l     A full textual representation of the day of the week                      Sunday to Saturday
3335   N     ISO-8601 numeric representation of the day of the week                    1 (for Monday) through 7 (for Sunday)
3336   S     English ordinal suffix for the day of the month, 2 characters             st, nd, rd or th. Works well with j
3337   w     Numeric representation of the day of the week                             0 (for Sunday) to 6 (for Saturday)
3338   z     The day of the year (starting from 0)                                     0 to 364 (365 in leap years)
3339   W     ISO-8601 week number of year, weeks starting on Monday                    01 to 53
3340   F     A full textual representation of a month, such as January or March        January to December
3341   m     Numeric representation of a month, with leading zeros                     01 to 12
3342   M     A short textual representation of a month                                 Jan to Dec
3343   n     Numeric representation of a month, without leading zeros                  1 to 12
3344   t     Number of days in the given month                                         28 to 31
3345   L     Whether it&#39;s a leap year                                                  1 if it is a leap year, 0 otherwise.
3346   o     ISO-8601 year number (identical to (Y), but if the ISO week number (W)    Examples: 1998 or 2004
3347         belongs to the previous or next year, that year is used instead)
3348   Y     A full numeric representation of a year, 4 digits                         Examples: 1999 or 2003
3349   y     A two digit representation of a year                                      Examples: 99 or 03
3350   a     Lowercase Ante meridiem and Post meridiem                                 am or pm
3351   A     Uppercase Ante meridiem and Post meridiem                                 AM or PM
3352   g     12-hour format of an hour without leading zeros                           1 to 12
3353   G     24-hour format of an hour without leading zeros                           0 to 23
3354   h     12-hour format of an hour with leading zeros                              01 to 12
3355   H     24-hour format of an hour with leading zeros                              00 to 23
3356   i     Minutes, with leading zeros                                               00 to 59
3357   s     Seconds, with leading zeros                                               00 to 59
3358   u     Decimal fraction of a second                                              Examples:
3359         (minimum 1 digit, arbitrary number of digits allowed)                     001 (i.e. 0.001s) or
3360                                                                                   100 (i.e. 0.100s) or
3361                                                                                   999 (i.e. 0.999s) or
3362                                                                                   999876543210 (i.e. 0.999876543210s)
3363   O     Difference to Greenwich time (GMT) in hours and minutes                   Example: +1030
3364   P     Difference to Greenwich time (GMT) with colon between hours and minutes   Example: -08:00
3365   T     Timezone abbreviation of the machine running the code                     Examples: EST, MDT, PDT ...
3366   Z     Timezone offset in seconds (negative if west of UTC, positive if east)    -43200 to 50400
3367   c     ISO 8601 date
3368         Notes:                                                                    Examples:
3369         1) If unspecified, the month / day defaults to the current month / day,   1991 or
3370            the time defaults to midnight, while the timezone defaults to the      1992-10 or
3371            browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
3372            and minutes. The "T" delimiter, seconds, milliseconds and timezone     1994-08-19T16:20+01:00 or
3373            are optional.                                                          1995-07-18T17:21:28-02:00 or
3374         2) The decimal fraction of a second, if specified, must contain at        1996-06-17T18:22:29.98765+03:00 or
3375            least 1 digit (there is no limit to the maximum number                 1997-05-16T19:23:30,12345-0400 or
3376            of digits allowed), and may be delimited by either a '.' or a ','      1998-04-15T20:24:31.2468Z or
3377         Refer to the examples on the right for the various levels of              1999-03-14T20:24:32Z or
3378         date-time granularity which are supported, or see                         2000-02-13T21:25:33
3379         http://www.w3.org/TR/NOTE-datetime for more info.                         2001-01-12 22:26:34
3380   U     Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)                1193432466 or -2138434463
3381   MS    Microsoft AJAX serialized dates                                           \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
3382                                                                                   \/Date(1238606590509+0800)\/
3383 </pre>
3384  *
3385  * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
3386  * <pre><code>
3387 // Sample date:
3388 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
3389
3390 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
3391 console.log(Ext.Date.format(dt, 'Y-m-d'));                          // 2007-01-10
3392 console.log(Ext.Date.format(dt, 'F j, Y, g:i a'));                  // January 10, 2007, 3:05 pm
3393 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
3394 </code></pre>
3395  *
3396  * Here are some standard date/time patterns that you might find helpful.  They
3397  * are not part of the source of Ext.Date, but to use them you can simply copy this
3398  * block of code into any script that is included after Ext.Date and they will also become
3399  * globally available on the Date object.  Feel free to add or remove patterns as needed in your code.
3400  * <pre><code>
3401 Ext.Date.patterns = {
3402     ISO8601Long:"Y-m-d H:i:s",
3403     ISO8601Short:"Y-m-d",
3404     ShortDate: "n/j/Y",
3405     LongDate: "l, F d, Y",
3406     FullDateTime: "l, F d, Y g:i:s A",
3407     MonthDay: "F d",
3408     ShortTime: "g:i A",
3409     LongTime: "g:i:s A",
3410     SortableDateTime: "Y-m-d\\TH:i:s",
3411     UniversalSortableDateTime: "Y-m-d H:i:sO",
3412     YearMonth: "F, Y"
3413 };
3414 </code></pre>
3415  *
3416  * Example usage:
3417  * <pre><code>
3418 var dt = new Date();
3419 console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
3420 </code></pre>
3421  * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
3422  * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
3423  * @singleton
3424  */
3425
3426 /*
3427  * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
3428  * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
3429  * They generate precompiled functions from format patterns instead of parsing and
3430  * processing each pattern every time a date is formatted. These functions are available
3431  * on every Date object.
3432  */
3433
3434 (function() {
3435
3436 // create private copy of Ext's Ext.util.Format.format() method
3437 // - to remove unnecessary dependency
3438 // - to resolve namespace conflict with MS-Ajax's implementation
3439 function xf(format) {
3440     var args = Array.prototype.slice.call(arguments, 1);
3441     return format.replace(/\{(\d+)\}/g, function(m, i) {
3442         return args[i];
3443     });
3444 }
3445
3446 Ext.Date = {
3447     /**
3448      * Returns the current timestamp
3449      * @return {Date} The current timestamp
3450      * @method
3451      */
3452     now: Date.now || function() {
3453         return +new Date();
3454     },
3455
3456     /**
3457      * @private
3458      * Private for now
3459      */
3460     toString: function(date) {
3461         var pad = Ext.String.leftPad;
3462
3463         return date.getFullYear() + "-"
3464             + pad(date.getMonth() + 1, 2, '0') + "-"
3465             + pad(date.getDate(), 2, '0') + "T"
3466             + pad(date.getHours(), 2, '0') + ":"
3467             + pad(date.getMinutes(), 2, '0') + ":"
3468             + pad(date.getSeconds(), 2, '0');
3469     },
3470
3471     /**
3472      * Returns the number of milliseconds between two dates
3473      * @param {Date} dateA The first date
3474      * @param {Date} dateB (optional) The second date, defaults to now
3475      * @return {Number} The difference in milliseconds
3476      */
3477     getElapsed: function(dateA, dateB) {
3478         return Math.abs(dateA - (dateB || new Date()));
3479     },
3480
3481     /**
3482      * Global flag which determines if strict date parsing should be used.
3483      * Strict date parsing will not roll-over invalid dates, which is the
3484      * default behaviour of javascript Date objects.
3485      * (see {@link #parse} for more information)
3486      * Defaults to <tt>false</tt>.
3487      * @type Boolean
3488     */
3489     useStrict: false,
3490
3491     // private
3492     formatCodeToRegex: function(character, currentGroup) {
3493         // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
3494         var p = utilDate.parseCodes[character];
3495
3496         if (p) {
3497           p = typeof p == 'function'? p() : p;
3498           utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
3499         }
3500
3501         return p ? Ext.applyIf({
3502           c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
3503         }, p) : {
3504             g: 0,
3505             c: null,
3506             s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
3507         };
3508     },
3509
3510     /**
3511      * <p>An object hash in which each property is a date parsing function. The property name is the
3512      * format string which that function parses.</p>
3513      * <p>This object is automatically populated with date parsing functions as
3514      * date formats are requested for Ext standard formatting strings.</p>
3515      * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
3516      * may be used as a format string to {@link #parse}.<p>
3517      * <p>Example:</p><pre><code>
3518 Ext.Date.parseFunctions['x-date-format'] = myDateParser;
3519 </code></pre>
3520      * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
3521      * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
3522      * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
3523      * (i.e. prevent javascript Date "rollover") (The default must be false).
3524      * Invalid date strings should return null when parsed.</div></li>
3525      * </ul></div></p>
3526      * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
3527      * formatting function must be placed into the {@link #formatFunctions} property.
3528      * @property parseFunctions
3529      * @type Object
3530      */
3531     parseFunctions: {
3532         "MS": function(input, strict) {
3533             // note: the timezone offset is ignored since the MS Ajax server sends
3534             // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
3535             var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
3536             var r = (input || '').match(re);
3537             return r? new Date(((r[1] || '') + r[2]) * 1) : null;
3538         }
3539     },
3540     parseRegexes: [],
3541
3542     /**
3543      * <p>An object hash in which each property is a date formatting function. The property name is the
3544      * format string which corresponds to the produced formatted date string.</p>
3545      * <p>This object is automatically populated with date formatting functions as
3546      * date formats are requested for Ext standard formatting strings.</p>
3547      * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
3548      * may be used as a format string to {@link #format}. Example:</p><pre><code>
3549 Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
3550 </code></pre>
3551      * <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>
3552      * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
3553      * </ul></div></p>
3554      * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
3555      * parsing function must be placed into the {@link #parseFunctions} property.
3556      * @property formatFunctions
3557      * @type Object
3558      */
3559     formatFunctions: {
3560         "MS": function() {
3561             // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
3562             return '\\/Date(' + this.getTime() + ')\\/';
3563         }
3564     },
3565
3566     y2kYear : 50,
3567
3568     /**
3569      * Date interval constant
3570      * @type String
3571      */
3572     MILLI : "ms",
3573
3574     /**
3575      * Date interval constant
3576      * @type String
3577      */
3578     SECOND : "s",
3579
3580     /**
3581      * Date interval constant
3582      * @type String
3583      */
3584     MINUTE : "mi",
3585
3586     /** Date interval constant
3587      * @type String
3588      */
3589     HOUR : "h",
3590
3591     /**
3592      * Date interval constant
3593      * @type String
3594      */
3595     DAY : "d",
3596
3597     /**
3598      * Date interval constant
3599      * @type String
3600      */
3601     MONTH : "mo",
3602
3603     /**
3604      * Date interval constant
3605      * @type String
3606      */
3607     YEAR : "y",
3608
3609     /**
3610      * <p>An object hash containing default date values used during date parsing.</p>
3611      * <p>The following properties are available:<div class="mdetail-params"><ul>
3612      * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
3613      * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
3614      * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
3615      * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
3616      * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
3617      * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
3618      * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
3619      * </ul></div></p>
3620      * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
3621      * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
3622      * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
3623      * It is the responsiblity of the developer to account for this.</b></p>
3624      * Example Usage:
3625      * <pre><code>
3626 // set default day value to the first day of the month
3627 Ext.Date.defaults.d = 1;
3628
3629 // parse a February date string containing only year and month values.
3630 // setting the default day value to 1 prevents weird date rollover issues
3631 // when attempting to parse the following date string on, for example, March 31st 2009.
3632 Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
3633 </code></pre>
3634      * @property defaults
3635      * @type Object
3636      */
3637     defaults: {},
3638
3639     /**
3640      * @property {String[]} dayNames
3641      * An array of textual day names.
3642      * Override these values for international dates.
3643      * Example:
3644      * <pre><code>
3645 Ext.Date.dayNames = [
3646     'SundayInYourLang',
3647     'MondayInYourLang',
3648     ...
3649 ];
3650 </code></pre>
3651      */
3652     dayNames : [
3653         "Sunday",
3654         "Monday",
3655         "Tuesday",
3656         "Wednesday",
3657         "Thursday",
3658         "Friday",
3659         "Saturday"
3660     ],
3661
3662     /**
3663      * @property {String[]} monthNames
3664      * An array of textual month names.
3665      * Override these values for international dates.
3666      * Example:
3667      * <pre><code>
3668 Ext.Date.monthNames = [
3669     'JanInYourLang',
3670     'FebInYourLang',
3671     ...
3672 ];
3673 </code></pre>
3674      */
3675     monthNames : [
3676         "January",
3677         "February",
3678         "March",
3679         "April",
3680         "May",
3681         "June",
3682         "July",
3683         "August",
3684         "September",
3685         "October",
3686         "November",
3687         "December"
3688     ],
3689
3690     /**
3691      * @property {Object} monthNumbers
3692      * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
3693      * Override these values for international dates.
3694      * Example:
3695      * <pre><code>
3696 Ext.Date.monthNumbers = {
3697     'ShortJanNameInYourLang':0,
3698     'ShortFebNameInYourLang':1,
3699     ...
3700 };
3701 </code></pre>
3702      */
3703     monthNumbers : {
3704         Jan:0,
3705         Feb:1,
3706         Mar:2,
3707         Apr:3,
3708         May:4,
3709         Jun:5,
3710         Jul:6,
3711         Aug:7,
3712         Sep:8,
3713         Oct:9,
3714         Nov:10,
3715         Dec:11
3716     },
3717     /**
3718      * @property {String} defaultFormat
3719      * <p>The date format string that the {@link Ext.util.Format#dateRenderer}
3720      * and {@link Ext.util.Format#date} functions use.  See {@link Ext.Date} for details.</p>
3721      * <p>This may be overridden in a locale file.</p>
3722      */
3723     defaultFormat : "m/d/Y",
3724     /**
3725      * Get the short month name for the given month number.
3726      * Override this function for international dates.
3727      * @param {Number} month A zero-based javascript month number.
3728      * @return {String} The short month name.
3729      */
3730     getShortMonthName : function(month) {
3731         return utilDate.monthNames[month].substring(0, 3);
3732     },
3733
3734     /**
3735      * Get the short day name for the given day number.
3736      * Override this function for international dates.
3737      * @param {Number} day A zero-based javascript day number.
3738      * @return {String} The short day name.
3739      */
3740     getShortDayName : function(day) {
3741         return utilDate.dayNames[day].substring(0, 3);
3742     },
3743
3744     /**
3745      * Get the zero-based javascript month number for the given short/full month name.
3746      * Override this function for international dates.
3747      * @param {String} name The short/full month name.
3748      * @return {Number} The zero-based javascript month number.
3749      */
3750     getMonthNumber : function(name) {
3751         // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
3752         return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
3753     },
3754
3755     /**
3756      * Checks if the specified format contains hour information
3757      * @param {String} format The format to check
3758      * @return {Boolean} True if the format contains hour information
3759      * @method
3760      */
3761     formatContainsHourInfo : (function(){
3762         var stripEscapeRe = /(\\.)/g,
3763             hourInfoRe = /([gGhHisucUOPZ]|MS)/;
3764         return function(format){
3765             return hourInfoRe.test(format.replace(stripEscapeRe, ''));
3766         };
3767     })(),
3768
3769     /**
3770      * Checks if the specified format contains information about
3771      * anything other than the time.
3772      * @param {String} format The format to check
3773      * @return {Boolean} True if the format contains information about
3774      * date/day information.
3775      * @method
3776      */
3777     formatContainsDateInfo : (function(){
3778         var stripEscapeRe = /(\\.)/g,
3779             dateInfoRe = /([djzmnYycU]|MS)/;
3780
3781         return function(format){
3782             return dateInfoRe.test(format.replace(stripEscapeRe, ''));
3783         };
3784     })(),
3785
3786     /**
3787      * The base format-code to formatting-function hashmap used by the {@link #format} method.
3788      * Formatting functions are strings (or functions which return strings) which
3789      * will return the appropriate value when evaluated in the context of the Date object
3790      * from which the {@link #format} method is called.
3791      * Add to / override these mappings for custom date formatting.
3792      * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
3793      * Example:
3794      * <pre><code>
3795 Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
3796 console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
3797 </code></pre>
3798      * @type Object
3799      */
3800     formatCodes : {
3801         d: "Ext.String.leftPad(this.getDate(), 2, '0')",
3802         D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
3803         j: "this.getDate()",
3804         l: "Ext.Date.dayNames[this.getDay()]",
3805         N: "(this.getDay() ? this.getDay() : 7)",
3806         S: "Ext.Date.getSuffix(this)",
3807         w: "this.getDay()",
3808         z: "Ext.Date.getDayOfYear(this)",
3809         W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
3810         F: "Ext.Date.monthNames[this.getMonth()]",
3811         m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
3812         M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
3813         n: "(this.getMonth() + 1)",
3814         t: "Ext.Date.getDaysInMonth(this)",
3815         L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
3816         o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
3817         Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
3818         y: "('' + this.getFullYear()).substring(2, 4)",
3819         a: "(this.getHours() < 12 ? 'am' : 'pm')",
3820         A: "(this.getHours() < 12 ? 'AM' : 'PM')",
3821         g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
3822         G: "this.getHours()",
3823         h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
3824         H: "Ext.String.leftPad(this.getHours(), 2, '0')",
3825         i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
3826         s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
3827         u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
3828         O: "Ext.Date.getGMTOffset(this)",
3829         P: "Ext.Date.getGMTOffset(this, true)",
3830         T: "Ext.Date.getTimezone(this)",
3831         Z: "(this.getTimezoneOffset() * -60)",
3832
3833         c: function() { // ISO-8601 -- GMT format
3834             for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
3835                 var e = c.charAt(i);
3836                 code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
3837             }
3838             return code.join(" + ");
3839         },
3840         /*
3841         c: function() { // ISO-8601 -- UTC format
3842             return [
3843               "this.getUTCFullYear()", "'-'",
3844               "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
3845               "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
3846               "'T'",
3847               "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
3848               "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
3849               "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
3850               "'Z'"
3851             ].join(" + ");
3852         },
3853         */
3854
3855         U: "Math.round(this.getTime() / 1000)"
3856     },
3857
3858     /**
3859      * Checks if the passed Date parameters will cause a javascript Date "rollover".
3860      * @param {Number} year 4-digit year
3861      * @param {Number} month 1-based month-of-year
3862      * @param {Number} day Day of month
3863      * @param {Number} hour (optional) Hour
3864      * @param {Number} minute (optional) Minute
3865      * @param {Number} second (optional) Second
3866      * @param {Number} millisecond (optional) Millisecond
3867      * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
3868      */
3869     isValid : function(y, m, d, h, i, s, ms) {
3870         // setup defaults
3871         h = h || 0;
3872         i = i || 0;
3873         s = s || 0;
3874         ms = ms || 0;
3875
3876         // Special handling for year < 100
3877         var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
3878
3879         return y == dt.getFullYear() &&
3880             m == dt.getMonth() + 1 &&
3881             d == dt.getDate() &&
3882             h == dt.getHours() &&
3883             i == dt.getMinutes() &&
3884             s == dt.getSeconds() &&
3885             ms == dt.getMilliseconds();
3886     },
3887
3888     /**
3889      * Parses the passed string using the specified date format.
3890      * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
3891      * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
3892      * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
3893      * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
3894      * Keep in mind that the input date string must precisely match the specified format string
3895      * in order for the parse operation to be successful (failed parse operations return a null value).
3896      * <p>Example:</p><pre><code>
3897 //dt = Fri May 25 2007 (current date)
3898 var dt = new Date();
3899
3900 //dt = Thu May 25 2006 (today&#39;s month/day in 2006)
3901 dt = Ext.Date.parse("2006", "Y");
3902
3903 //dt = Sun Jan 15 2006 (all date parts specified)
3904 dt = Ext.Date.parse("2006-01-15", "Y-m-d");
3905
3906 //dt = Sun Jan 15 2006 15:20:01
3907 dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
3908
3909 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
3910 dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
3911 </code></pre>
3912      * @param {String} input The raw date string.
3913      * @param {String} format The expected date string format.
3914      * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
3915                         (defaults to false). Invalid date strings will return null when parsed.
3916      * @return {Date} The parsed Date.
3917      */
3918     parse : function(input, format, strict) {
3919         var p = utilDate.parseFunctions;
3920         if (p[format] == null) {
3921             utilDate.createParser(format);
3922         }
3923         return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
3924     },
3925
3926     // Backwards compat
3927     parseDate: function(input, format, strict){
3928         return utilDate.parse(input, format, strict);
3929     },
3930
3931
3932     // private
3933     getFormatCode : function(character) {
3934         var f = utilDate.formatCodes[character];
3935
3936         if (f) {
3937           f = typeof f == 'function'? f() : f;
3938           utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
3939         }
3940
3941         // note: unknown characters are treated as literals
3942         return f || ("'" + Ext.String.escape(character) + "'");
3943     },
3944
3945     // private
3946     createFormat : function(format) {
3947         var code = [],
3948             special = false,
3949             ch = '';
3950
3951         for (var i = 0; i < format.length; ++i) {
3952             ch = format.charAt(i);
3953             if (!special && ch == "\\") {
3954                 special = true;
3955             } else if (special) {
3956                 special = false;
3957                 code.push("'" + Ext.String.escape(ch) + "'");
3958             } else {
3959                 code.push(utilDate.getFormatCode(ch));
3960             }
3961         }
3962         utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
3963     },
3964
3965     // private
3966     createParser : (function() {
3967         var code = [
3968             "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
3969                 "def = Ext.Date.defaults,",
3970                 "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
3971
3972             "if(results){",
3973                 "{1}",
3974
3975                 "if(u != null){", // i.e. unix time is defined
3976                     "v = new Date(u * 1000);", // give top priority to UNIX time
3977                 "}else{",
3978                     // create Date object representing midnight of the current day;
3979                     // this will provide us with our date defaults
3980                     // (note: clearTime() handles Daylight Saving Time automatically)
3981                     "dt = Ext.Date.clearTime(new Date);",
3982
3983                     // date calculations (note: these calculations create a dependency on Ext.Number.from())
3984                     "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
3985                     "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
3986                     "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
3987
3988                     // time calculations (note: these calculations create a dependency on Ext.Number.from())
3989                     "h  = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
3990                     "i  = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
3991                     "s  = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
3992                     "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
3993
3994                     "if(z >= 0 && y >= 0){",
3995                         // both the year and zero-based day of year are defined and >= 0.
3996                         // these 2 values alone provide sufficient info to create a full date object
3997
3998                         // create Date object representing January 1st for the given year
3999                         // handle years < 100 appropriately
4000                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
4001
4002                         // then add day of year, checking for Date "rollover" if necessary
4003                         "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
4004                     "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
4005                         "v = null;", // invalid date, so return null
4006                     "}else{",
4007                         // plain old Date object
4008                         // handle years < 100 properly
4009                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
4010                     "}",
4011                 "}",
4012             "}",
4013
4014             "if(v){",
4015                 // favour UTC offset over GMT offset
4016                 "if(zz != null){",
4017                     // reset to UTC, then add offset
4018                     "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
4019                 "}else if(o){",
4020                     // reset to GMT, then add offset
4021                     "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
4022                 "}",
4023             "}",
4024
4025             "return v;"
4026         ].join('\n');
4027
4028         return function(format) {
4029             var regexNum = utilDate.parseRegexes.length,
4030                 currentGroup = 1,
4031                 calc = [],
4032                 regex = [],
4033                 special = false,
4034                 ch = "";
4035
4036             for (var i = 0; i < format.length; ++i) {
4037                 ch = format.charAt(i);
4038                 if (!special && ch == "\\") {
4039                     special = true;
4040                 } else if (special) {
4041                     special = false;
4042                     regex.push(Ext.String.escape(ch));
4043                 } else {
4044                     var obj = utilDate.formatCodeToRegex(ch, currentGroup);
4045                     currentGroup += obj.g;
4046                     regex.push(obj.s);
4047                     if (obj.g && obj.c) {
4048                         calc.push(obj.c);
4049                     }
4050                 }
4051             }
4052
4053             utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
4054             utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
4055         };
4056     })(),
4057
4058     // private
4059     parseCodes : {
4060         /*
4061          * Notes:
4062          * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
4063          * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
4064          * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
4065          */
4066         d: {
4067             g:1,
4068             c:"d = parseInt(results[{0}], 10);\n",
4069             s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
4070         },
4071         j: {
4072             g:1,
4073             c:"d = parseInt(results[{0}], 10);\n",
4074             s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
4075         },
4076         D: function() {
4077             for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
4078             return {
4079                 g:0,
4080                 c:null,
4081                 s:"(?:" + a.join("|") +")"
4082             };
4083         },
4084         l: function() {
4085             return {
4086                 g:0,
4087                 c:null,
4088                 s:"(?:" + utilDate.dayNames.join("|") + ")"
4089             };
4090         },
4091         N: {
4092             g:0,
4093             c:null,
4094             s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
4095         },
4096         S: {
4097             g:0,
4098             c:null,
4099             s:"(?:st|nd|rd|th)"
4100         },
4101         w: {
4102             g:0,
4103             c:null,
4104             s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
4105         },
4106         z: {
4107             g:1,
4108             c:"z = parseInt(results[{0}], 10);\n",
4109             s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
4110         },
4111         W: {
4112             g:0,
4113             c:null,
4114             s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
4115         },
4116         F: function() {
4117             return {
4118                 g:1,
4119                 c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
4120                 s:"(" + utilDate.monthNames.join("|") + ")"
4121             };
4122         },
4123         M: function() {
4124             for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
4125             return Ext.applyIf({
4126                 s:"(" + a.join("|") + ")"
4127             }, utilDate.formatCodeToRegex("F"));
4128         },
4129         m: {
4130             g:1,
4131             c:"m = parseInt(results[{0}], 10) - 1;\n",
4132             s:"(\\d{2})" // month number with leading zeros (01 - 12)
4133         },
4134         n: {
4135             g:1,
4136             c:"m = parseInt(results[{0}], 10) - 1;\n",
4137             s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
4138         },
4139         t: {
4140             g:0,
4141             c:null,
4142             s:"(?:\\d{2})" // no. of days in the month (28 - 31)
4143         },
4144         L: {
4145             g:0,
4146             c:null,
4147             s:"(?:1|0)"
4148         },
4149         o: function() {
4150             return utilDate.formatCodeToRegex("Y");
4151         },
4152         Y: {
4153             g:1,
4154             c:"y = parseInt(results[{0}], 10);\n",
4155             s:"(\\d{4})" // 4-digit year
4156         },
4157         y: {
4158             g:1,
4159             c:"var ty = parseInt(results[{0}], 10);\n"
4160                 + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
4161             s:"(\\d{1,2})"
4162         },
4163         /*
4164          * In the am/pm parsing routines, we allow both upper and lower case
4165          * even though it doesn't exactly match the spec. It gives much more flexibility
4166          * in being able to specify case insensitive regexes.
4167          */
4168         a: {
4169             g:1,
4170             c:"if (/(am)/i.test(results[{0}])) {\n"
4171                 + "if (!h || h == 12) { h = 0; }\n"
4172                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
4173             s:"(am|pm|AM|PM)"
4174         },
4175         A: {
4176             g:1,
4177             c:"if (/(am)/i.test(results[{0}])) {\n"
4178                 + "if (!h || h == 12) { h = 0; }\n"
4179                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
4180             s:"(AM|PM|am|pm)"
4181         },
4182         g: function() {
4183             return utilDate.formatCodeToRegex("G");
4184         },
4185         G: {
4186             g:1,
4187             c:"h = parseInt(results[{0}], 10);\n",
4188             s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
4189         },
4190         h: function() {
4191             return utilDate.formatCodeToRegex("H");
4192         },
4193         H: {
4194             g:1,
4195             c:"h = parseInt(results[{0}], 10);\n",
4196             s:"(\\d{2})" //  24-hr format of an hour with leading zeroes (00 - 23)
4197         },
4198         i: {
4199             g:1,
4200             c:"i = parseInt(results[{0}], 10);\n",
4201             s:"(\\d{2})" // minutes with leading zeros (00 - 59)
4202         },
4203         s: {
4204             g:1,
4205             c:"s = parseInt(results[{0}], 10);\n",
4206             s:"(\\d{2})" // seconds with leading zeros (00 - 59)
4207         },
4208         u: {
4209             g:1,
4210             c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
4211             s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
4212         },
4213         O: {
4214             g:1,
4215             c:[
4216                 "o = results[{0}];",
4217                 "var sn = o.substring(0,1),", // get + / - sign
4218                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
4219                     "mn = o.substring(3,5) % 60;", // get minutes
4220                 "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
4221             ].join("\n"),
4222             s: "([+\-]\\d{4})" // GMT offset in hrs and mins
4223         },
4224         P: {
4225             g:1,
4226             c:[
4227                 "o = results[{0}];",
4228                 "var sn = o.substring(0,1),", // get + / - sign
4229                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
4230                     "mn = o.substring(4,6) % 60;", // get minutes
4231                 "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
4232             ].join("\n"),
4233             s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
4234         },
4235         T: {
4236             g:0,
4237             c:null,
4238             s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
4239         },
4240         Z: {
4241             g:1,
4242             c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
4243                   + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
4244             s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
4245         },
4246         c: function() {
4247             var calc = [],
4248                 arr = [
4249                     utilDate.formatCodeToRegex("Y", 1), // year
4250                     utilDate.formatCodeToRegex("m", 2), // month
4251                     utilDate.formatCodeToRegex("d", 3), // day
4252                     utilDate.formatCodeToRegex("h", 4), // hour
4253                     utilDate.formatCodeToRegex("i", 5), // minute
4254                     utilDate.formatCodeToRegex("s", 6), // second
4255                     {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)
4256                     {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
4257                         "if(results[8]) {", // timezone specified
4258                             "if(results[8] == 'Z'){",
4259                                 "zz = 0;", // UTC
4260                             "}else if (results[8].indexOf(':') > -1){",
4261                                 utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
4262                             "}else{",
4263                                 utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
4264                             "}",
4265                         "}"
4266                     ].join('\n')}
4267                 ];
4268
4269             for (var i = 0, l = arr.length; i < l; ++i) {
4270                 calc.push(arr[i].c);
4271             }
4272
4273             return {
4274                 g:1,
4275                 c:calc.join(""),
4276                 s:[
4277                     arr[0].s, // year (required)
4278                     "(?:", "-", arr[1].s, // month (optional)
4279                         "(?:", "-", arr[2].s, // day (optional)
4280                             "(?:",
4281                                 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
4282                                 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
4283                                 "(?::", arr[5].s, ")?", // seconds (optional)
4284                                 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
4285                                 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
4286                             ")?",
4287                         ")?",
4288                     ")?"
4289                 ].join("")
4290             };
4291         },
4292         U: {
4293             g:1,
4294             c:"u = parseInt(results[{0}], 10);\n",
4295             s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
4296         }
4297     },
4298
4299     //Old Ext.Date prototype methods.
4300     // private
4301     dateFormat: function(date, format) {
4302         return utilDate.format(date, format);
4303     },
4304
4305     /**
4306      * Formats a date given the supplied format string.
4307      * @param {Date} date The date to format
4308      * @param {String} format The format string
4309      * @return {String} The formatted date
4310      */
4311     format: function(date, format) {
4312         if (utilDate.formatFunctions[format] == null) {
4313             utilDate.createFormat(format);
4314         }
4315         var result = utilDate.formatFunctions[format].call(date);
4316         return result + '';
4317     },
4318
4319     /**
4320      * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
4321      *
4322      * Note: The date string returned by the javascript Date object's toString() method varies
4323      * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
4324      * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
4325      * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
4326      * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
4327      * from the GMT offset portion of the date string.
4328      * @param {Date} date The date
4329      * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
4330      */
4331     getTimezone : function(date) {
4332         // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
4333         //
4334         // Opera  : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
4335         // 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)
4336         // FF     : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
4337         // IE     : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
4338         // IE     : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
4339         //
4340         // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
4341         // step 1: (?:\((.*)\) -- find timezone in parentheses
4342         // 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
4343         // step 3: remove all non uppercase characters found in step 1 and 2
4344         return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
4345     },
4346
4347     /**
4348      * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
4349      * @param {Date} date The date
4350      * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
4351      * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
4352      */
4353     getGMTOffset : function(date, colon) {
4354         var offset = date.getTimezoneOffset();
4355         return (offset > 0 ? "-" : "+")
4356             + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
4357             + (colon ? ":" : "")
4358             + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
4359     },
4360
4361     /**
4362      * Get the numeric day number of the year, adjusted for leap year.
4363      * @param {Date} date The date
4364      * @return {Number} 0 to 364 (365 in leap years).
4365      */
4366     getDayOfYear: function(date) {
4367         var num = 0,
4368             d = Ext.Date.clone(date),
4369             m = date.getMonth(),
4370             i;
4371
4372         for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
4373             num += utilDate.getDaysInMonth(d);
4374         }
4375         return num + date.getDate() - 1;
4376     },
4377
4378     /**
4379      * Get the numeric ISO-8601 week number of the year.
4380      * (equivalent to the format specifier 'W', but without a leading zero).
4381      * @param {Date} date The date
4382      * @return {Number} 1 to 53
4383      * @method
4384      */
4385     getWeekOfYear : (function() {
4386         // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
4387         var ms1d = 864e5, // milliseconds in a day
4388             ms7d = 7 * ms1d; // milliseconds in a week
4389
4390         return function(date) { // return a closure so constants get calculated only once
4391             var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
4392                 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
4393                 Wyr = new Date(AWN * ms7d).getUTCFullYear();
4394
4395             return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
4396         };
4397     })(),
4398
4399     /**
4400      * Checks if the current date falls within a leap year.
4401      * @param {Date} date The date
4402      * @return {Boolean} True if the current date falls within a leap year, false otherwise.
4403      */
4404     isLeapYear : function(date) {
4405         var year = date.getFullYear();
4406         return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
4407     },
4408
4409     /**
4410      * Get the first day of the current month, adjusted for leap year.  The returned value
4411      * is the numeric day index within the week (0-6) which can be used in conjunction with
4412      * the {@link #monthNames} array to retrieve the textual day name.
4413      * Example:
4414      * <pre><code>
4415 var dt = new Date('1/10/2007'),
4416     firstDay = Ext.Date.getFirstDayOfMonth(dt);
4417 console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
4418      * </code></pre>
4419      * @param {Date} date The date
4420      * @return {Number} The day number (0-6).
4421      */
4422     getFirstDayOfMonth : function(date) {
4423         var day = (date.getDay() - (date.getDate() - 1)) % 7;
4424         return (day < 0) ? (day + 7) : day;
4425     },
4426
4427     /**
4428      * Get the last day of the current month, adjusted for leap year.  The returned value
4429      * is the numeric day index within the week (0-6) which can be used in conjunction with
4430      * the {@link #monthNames} array to retrieve the textual day name.
4431      * Example:
4432      * <pre><code>
4433 var dt = new Date('1/10/2007'),
4434     lastDay = Ext.Date.getLastDayOfMonth(dt);
4435 console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
4436      * </code></pre>
4437      * @param {Date} date The date
4438      * @return {Number} The day number (0-6).
4439      */
4440     getLastDayOfMonth : function(date) {
4441         return utilDate.getLastDateOfMonth(date).getDay();
4442     },
4443
4444
4445     /**
4446      * Get the date of the first day of the month in which this date resides.
4447      * @param {Date} date The date
4448      * @return {Date}
4449      */
4450     getFirstDateOfMonth : function(date) {
4451         return new Date(date.getFullYear(), date.getMonth(), 1);
4452     },
4453
4454     /**
4455      * Get the date of the last day of the month in which this date resides.
4456      * @param {Date} date The date
4457      * @return {Date}
4458      */
4459     getLastDateOfMonth : function(date) {
4460         return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
4461     },
4462
4463     /**
4464      * Get the number of days in the current month, adjusted for leap year.
4465      * @param {Date} date The date
4466      * @return {Number} The number of days in the month.
4467      * @method
4468      */
4469     getDaysInMonth: (function() {
4470         var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4471
4472         return function(date) { // return a closure for efficiency
4473             var m = date.getMonth();
4474
4475             return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
4476         };
4477     })(),
4478
4479     /**
4480      * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
4481      * @param {Date} date The date
4482      * @return {String} 'st, 'nd', 'rd' or 'th'.
4483      */
4484     getSuffix : function(date) {
4485         switch (date.getDate()) {
4486             case 1:
4487             case 21:
4488             case 31:
4489                 return "st";
4490             case 2:
4491             case 22:
4492                 return "nd";
4493             case 3:
4494             case 23:
4495                 return "rd";
4496             default:
4497                 return "th";
4498         }
4499     },
4500
4501     /**
4502      * Creates and returns a new Date instance with the exact same date value as the called instance.
4503      * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
4504      * variable will also be changed.  When the intention is to create a new variable that will not
4505      * modify the original instance, you should create a clone.
4506      *
4507      * Example of correctly cloning a date:
4508      * <pre><code>
4509 //wrong way:
4510 var orig = new Date('10/1/2006');
4511 var copy = orig;
4512 copy.setDate(5);
4513 console.log(orig);  //returns 'Thu Oct 05 2006'!
4514
4515 //correct way:
4516 var orig = new Date('10/1/2006'),
4517     copy = Ext.Date.clone(orig);
4518 copy.setDate(5);
4519 console.log(orig);  //returns 'Thu Oct 01 2006'
4520      * </code></pre>
4521      * @param {Date} date The date
4522      * @return {Date} The new Date instance.
4523      */
4524     clone : function(date) {
4525         return new Date(date.getTime());
4526     },
4527
4528     /**
4529      * Checks if the current date is affected by Daylight Saving Time (DST).
4530      * @param {Date} date The date
4531      * @return {Boolean} True if the current date is affected by DST.
4532      */
4533     isDST : function(date) {
4534         // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
4535         // courtesy of @geoffrey.mcgill
4536         return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
4537     },
4538
4539     /**
4540      * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
4541      * automatically adjusting for Daylight Saving Time (DST) where applicable.
4542      * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
4543      * @param {Date} date The date
4544      * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
4545      * @return {Date} this or the clone.
4546      */
4547     clearTime : function(date, clone) {
4548         if (clone) {
4549             return Ext.Date.clearTime(Ext.Date.clone(date));
4550         }
4551
4552         // get current date before clearing time
4553         var d = date.getDate();
4554
4555         // clear time
4556         date.setHours(0);
4557         date.setMinutes(0);
4558         date.setSeconds(0);
4559         date.setMilliseconds(0);
4560
4561         if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
4562             // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
4563             // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
4564
4565             // increment hour until cloned date == current date
4566             for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
4567
4568             date.setDate(d);
4569             date.setHours(c.getHours());
4570         }
4571
4572         return date;
4573     },
4574
4575     /**
4576      * Provides a convenient method for performing basic date arithmetic. This method
4577      * does not modify the Date instance being called - it creates and returns
4578      * a new Date instance containing the resulting date value.
4579      *
4580      * Examples:
4581      * <pre><code>
4582 // Basic usage:
4583 var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
4584 console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
4585
4586 // Negative values will be subtracted:
4587 var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
4588 console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
4589
4590      * </code></pre>
4591      *
4592      * @param {Date} date The date to modify
4593      * @param {String} interval A valid date interval enum value.
4594      * @param {Number} value The amount to add to the current date.
4595      * @return {Date} The new Date instance.
4596      */
4597     add : function(date, interval, value) {
4598         var d = Ext.Date.clone(date),
4599             Date = Ext.Date;
4600         if (!interval || value === 0) return d;
4601
4602         switch(interval.toLowerCase()) {
4603             case Ext.Date.MILLI:
4604                 d.setMilliseconds(d.getMilliseconds() + value);
4605                 break;
4606             case Ext.Date.SECOND:
4607                 d.setSeconds(d.getSeconds() + value);
4608                 break;
4609             case Ext.Date.MINUTE:
4610                 d.setMinutes(d.getMinutes() + value);
4611                 break;
4612             case Ext.Date.HOUR:
4613                 d.setHours(d.getHours() + value);
4614                 break;
4615             case Ext.Date.DAY:
4616                 d.setDate(d.getDate() + value);
4617                 break;
4618             case Ext.Date.MONTH:
4619                 var day = date.getDate();
4620                 if (day > 28) {
4621                     day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
4622                 }
4623                 d.setDate(day);
4624                 d.setMonth(date.getMonth() + value);
4625                 break;
4626             case Ext.Date.YEAR:
4627                 d.setFullYear(date.getFullYear() + value);
4628                 break;
4629         }
4630         return d;
4631     },
4632
4633     /**
4634      * Checks if a date falls on or between the given start and end dates.
4635      * @param {Date} date The date to check
4636      * @param {Date} start Start date
4637      * @param {Date} end End date
4638      * @return {Boolean} true if this date falls on or between the given start and end dates.
4639      */
4640     between : function(date, start, end) {
4641         var t = date.getTime();
4642         return start.getTime() <= t && t <= end.getTime();
4643     },
4644
4645     //Maintains compatibility with old static and prototype window.Date methods.
4646     compat: function() {
4647         var nativeDate = window.Date,
4648             p, u,
4649             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'],
4650             proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'];
4651
4652         //Append statics
4653         Ext.Array.forEach(statics, function(s) {
4654             nativeDate[s] = utilDate[s];
4655         });
4656
4657         //Append to prototype
4658         Ext.Array.forEach(proto, function(s) {
4659             nativeDate.prototype[s] = function() {
4660                 var args = Array.prototype.slice.call(arguments);
4661                 args.unshift(this);
4662                 return utilDate[s].apply(utilDate, args);
4663             };
4664         });
4665     }
4666 };
4667
4668 var utilDate = Ext.Date;
4669
4670 })();
4671
4672 /**
4673  * @author Jacky Nguyen <jacky@sencha.com>
4674  * @docauthor Jacky Nguyen <jacky@sencha.com>
4675  * @class Ext.Base
4676  *
4677  * The root of all classes created with {@link Ext#define}.
4678  *
4679  * Ext.Base is the building block of all Ext classes. All classes in Ext inherit from Ext.Base.
4680  * All prototype and static members of this class are inherited by all other classes.
4681  */
4682 (function(flexSetter) {
4683
4684 var Base = Ext.Base = function() {};
4685     Base.prototype = {
4686         $className: 'Ext.Base',
4687
4688         $class: Base,
4689
4690         /**
4691          * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
4692          * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
4693          * for a detailed comparison
4694          *
4695          *     Ext.define('My.Cat', {
4696          *         statics: {
4697          *             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4698          *         },
4699          *
4700          *         constructor: function() {
4701          *             alert(this.self.speciesName); / dependent on 'this'
4702          *
4703          *             return this;
4704          *         },
4705          *
4706          *         clone: function() {
4707          *             return new this.self();
4708          *         }
4709          *     });
4710          *
4711          *
4712          *     Ext.define('My.SnowLeopard', {
4713          *         extend: 'My.Cat',
4714          *         statics: {
4715          *             speciesName: 'Snow Leopard'         // My.SnowLeopard.speciesName = 'Snow Leopard'
4716          *         }
4717          *     });
4718          *
4719          *     var cat = new My.Cat();                     // alerts 'Cat'
4720          *     var snowLeopard = new My.SnowLeopard();     // alerts 'Snow Leopard'
4721          *
4722          *     var clone = snowLeopard.clone();
4723          *     alert(Ext.getClassName(clone));             // alerts 'My.SnowLeopard'
4724          *
4725          * @type Ext.Class
4726          * @protected
4727          */
4728         self: Base,
4729
4730         // Default constructor, simply returns `this`
4731         constructor: function() {
4732             return this;
4733         },
4734
4735         //<feature classSystem.config>
4736         /**
4737          * Initialize configuration for this class. a typical example:
4738          *
4739          *     Ext.define('My.awesome.Class', {
4740          *         // The default config
4741          *         config: {
4742          *             name: 'Awesome',
4743          *             isAwesome: true
4744          *         },
4745          *
4746          *         constructor: function(config) {
4747          *             this.initConfig(config);
4748          *
4749          *             return this;
4750          *         }
4751          *     });
4752          *
4753          *     var awesome = new My.awesome.Class({
4754          *         name: 'Super Awesome'
4755          *     });
4756          *
4757          *     alert(awesome.getName()); // 'Super Awesome'
4758          *
4759          * @protected
4760          * @param {Object} config
4761          * @return {Object} mixins The mixin prototypes as key - value pairs
4762          */
4763         initConfig: function(config) {
4764             if (!this.$configInited) {
4765                 this.config = Ext.Object.merge({}, this.config || {}, config || {});
4766
4767                 this.applyConfig(this.config);
4768
4769                 this.$configInited = true;
4770             }
4771
4772             return this;
4773         },
4774
4775         /**
4776          * @private
4777          */
4778         setConfig: function(config) {
4779             this.applyConfig(config || {});
4780
4781             return this;
4782         },
4783
4784         /**
4785          * @private
4786          */
4787         applyConfig: flexSetter(function(name, value) {
4788             var setter = 'set' + Ext.String.capitalize(name);
4789
4790             if (typeof this[setter] === 'function') {
4791                 this[setter].call(this, value);
4792             }
4793
4794             return this;
4795         }),
4796         //</feature>
4797
4798         /**
4799          * Call the parent's overridden method. For example:
4800          *
4801          *     Ext.define('My.own.A', {
4802          *         constructor: function(test) {
4803          *             alert(test);
4804          *         }
4805          *     });
4806          *
4807          *     Ext.define('My.own.B', {
4808          *         extend: 'My.own.A',
4809          *
4810          *         constructor: function(test) {
4811          *             alert(test);
4812          *
4813          *             this.callParent([test + 1]);
4814          *         }
4815          *     });
4816          *
4817          *     Ext.define('My.own.C', {
4818          *         extend: 'My.own.B',
4819          *
4820          *         constructor: function() {
4821          *             alert("Going to call parent's overriden constructor...");
4822          *
4823          *             this.callParent(arguments);
4824          *         }
4825          *     });
4826          *
4827          *     var a = new My.own.A(1); // alerts '1'
4828          *     var b = new My.own.B(1); // alerts '1', then alerts '2'
4829          *     var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
4830          *                              // alerts '2', then alerts '3'
4831          *
4832          * @protected
4833          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4834          * from the current method, for example: `this.callParent(arguments)`
4835          * @return {Object} Returns the result from the superclass' method
4836          */
4837         callParent: function(args) {
4838             var method = this.callParent.caller,
4839                 parentClass, methodName;
4840
4841             if (!method.$owner) {
4842                 if (!method.caller) {
4843                     Ext.Error.raise({
4844                         sourceClass: Ext.getClassName(this),
4845                         sourceMethod: "callParent",
4846                         msg: "Attempting to call a protected method from the public scope, which is not allowed"
4847                     });
4848                 }
4849
4850                 method = method.caller;
4851             }
4852
4853             parentClass = method.$owner.superclass;
4854             methodName = method.$name;
4855
4856             if (!(methodName in parentClass)) {
4857                 Ext.Error.raise({
4858                     sourceClass: Ext.getClassName(this),
4859                     sourceMethod: methodName,
4860                     msg: "this.callParent() was called but there's no such method (" + methodName +
4861                          ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"
4862                  });
4863             }
4864
4865             return parentClass[methodName].apply(this, args || []);
4866         },
4867
4868
4869         /**
4870          * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
4871          * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
4872          * `this` points to during run-time
4873          *
4874          *     Ext.define('My.Cat', {
4875          *         statics: {
4876          *             totalCreated: 0,
4877          *             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4878          *         },
4879          *
4880          *         constructor: function() {
4881          *             var statics = this.statics();
4882          *
4883          *             alert(statics.speciesName);     // always equals to 'Cat' no matter what 'this' refers to
4884          *                                             // equivalent to: My.Cat.speciesName
4885          *
4886          *             alert(this.self.speciesName);   // dependent on 'this'
4887          *
4888          *             statics.totalCreated++;
4889          *
4890          *             return this;
4891          *         },
4892          *
4893          *         clone: function() {
4894          *             var cloned = new this.self;                      // dependent on 'this'
4895          *
4896          *             cloned.groupName = this.statics().speciesName;   // equivalent to: My.Cat.speciesName
4897          *
4898          *             return cloned;
4899          *         }
4900          *     });
4901          *
4902          *
4903          *     Ext.define('My.SnowLeopard', {
4904          *         extend: 'My.Cat',
4905          *
4906          *         statics: {
4907          *             speciesName: 'Snow Leopard'     // My.SnowLeopard.speciesName = 'Snow Leopard'
4908          *         },
4909          *
4910          *         constructor: function() {
4911          *             this.callParent();
4912          *         }
4913          *     });
4914          *
4915          *     var cat = new My.Cat();                 // alerts 'Cat', then alerts 'Cat'
4916          *
4917          *     var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
4918          *
4919          *     var clone = snowLeopard.clone();
4920          *     alert(Ext.getClassName(clone));         // alerts 'My.SnowLeopard'
4921          *     alert(clone.groupName);                 // alerts 'Cat'
4922          *
4923          *     alert(My.Cat.totalCreated);             // alerts 3
4924          *
4925          * @protected
4926          * @return {Ext.Class}
4927          */
4928         statics: function() {
4929             var method = this.statics.caller,
4930                 self = this.self;
4931
4932             if (!method) {
4933                 return self;
4934             }
4935
4936             return method.$owner;
4937         },
4938
4939         /**
4940          * Call the original method that was previously overridden with {@link Ext.Base#override}
4941          *
4942          *     Ext.define('My.Cat', {
4943          *         constructor: function() {
4944          *             alert("I'm a cat!");
4945          *
4946          *             return this;
4947          *         }
4948          *     });
4949          *
4950          *     My.Cat.override({
4951          *         constructor: function() {
4952          *             alert("I'm going to be a cat!");
4953          *
4954          *             var instance = this.callOverridden();
4955          *
4956          *             alert("Meeeeoooowwww");
4957          *
4958          *             return instance;
4959          *         }
4960          *     });
4961          *
4962          *     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4963          *                               // alerts "I'm a cat!"
4964          *                               // alerts "Meeeeoooowwww"
4965          *
4966          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4967          * @return {Object} Returns the result after calling the overridden method
4968          * @protected
4969          */
4970         callOverridden: function(args) {
4971             var method = this.callOverridden.caller;
4972
4973             if (!method.$owner) {
4974                 Ext.Error.raise({
4975                     sourceClass: Ext.getClassName(this),
4976                     sourceMethod: "callOverridden",
4977                     msg: "Attempting to call a protected method from the public scope, which is not allowed"
4978                 });
4979             }
4980
4981             if (!method.$previous) {
4982                 Ext.Error.raise({
4983                     sourceClass: Ext.getClassName(this),
4984                     sourceMethod: "callOverridden",
4985                     msg: "this.callOverridden was called in '" + method.$name +
4986                          "' but this method has never been overridden"
4987                  });
4988             }
4989
4990             return method.$previous.apply(this, args || []);
4991         },
4992
4993         destroy: function() {}
4994     };
4995
4996     // These static properties will be copied to every newly created class with {@link Ext#define}
4997     Ext.apply(Ext.Base, {
4998         /**
4999          * Create a new instance of this Class.
5000          *
5001          *     Ext.define('My.cool.Class', {
5002          *         ...
5003          *     });
5004          *
5005          *     My.cool.Class.create({
5006          *         someConfig: true
5007          *     });
5008          *
5009          * All parameters are passed to the constructor of the class.
5010          *
5011          * @return {Object} the created instance.
5012          * @static
5013          * @inheritable
5014          */
5015         create: function() {
5016             return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
5017         },
5018
5019         /**
5020          * @private
5021          * @inheritable
5022          */
5023         own: function(name, value) {
5024             if (typeof value == 'function') {
5025                 this.ownMethod(name, value);
5026             }
5027             else {
5028                 this.prototype[name] = value;
5029             }
5030         },
5031
5032         /**
5033          * @private
5034          * @inheritable
5035          */
5036         ownMethod: function(name, fn) {
5037             var originalFn;
5038
5039             if (typeof fn.$owner !== 'undefined' && fn !== Ext.emptyFn) {
5040                 originalFn = fn;
5041
5042                 fn = function() {
5043                     return originalFn.apply(this, arguments);
5044                 };
5045             }
5046
5047             var className;
5048             className = Ext.getClassName(this);
5049             if (className) {
5050                 fn.displayName = className + '#' + name;
5051             }
5052             fn.$owner = this;
5053             fn.$name = name;
5054
5055             this.prototype[name] = fn;
5056         },
5057
5058         /**
5059          * Add / override static properties of this class.
5060          *
5061          *     Ext.define('My.cool.Class', {
5062          *         ...
5063          *     });
5064          *
5065          *     My.cool.Class.addStatics({
5066          *         someProperty: 'someValue',      // My.cool.Class.someProperty = 'someValue'
5067          *         method1: function() { ... },    // My.cool.Class.method1 = function() { ... };
5068          *         method2: function() { ... }     // My.cool.Class.method2 = function() { ... };
5069          *     });
5070          *
5071          * @param {Object} members
5072          * @return {Ext.Base} this
5073          * @static
5074          * @inheritable
5075          */
5076         addStatics: function(members) {
5077             for (var name in members) {
5078                 if (members.hasOwnProperty(name)) {
5079                     this[name] = members[name];
5080                 }
5081             }
5082
5083             return this;
5084         },
5085
5086         /**
5087          * @private
5088          * @param {Object} members
5089          */
5090         addInheritableStatics: function(members) {
5091             var inheritableStatics,
5092                 hasInheritableStatics,
5093                 prototype = this.prototype,
5094                 name, member;
5095
5096             inheritableStatics = prototype.$inheritableStatics;
5097             hasInheritableStatics = prototype.$hasInheritableStatics;
5098
5099             if (!inheritableStatics) {
5100                 inheritableStatics = prototype.$inheritableStatics = [];
5101                 hasInheritableStatics = prototype.$hasInheritableStatics = {};
5102             }
5103
5104             var className = Ext.getClassName(this);
5105
5106             for (name in members) {
5107                 if (members.hasOwnProperty(name)) {
5108                     member = members[name];
5109                     if (typeof member == 'function') {
5110                         member.displayName = className + '.' + name;
5111                     }
5112                     this[name] = member;
5113
5114                     if (!hasInheritableStatics[name]) {
5115                         hasInheritableStatics[name] = true;
5116                         inheritableStatics.push(name);
5117                     }
5118                 }
5119             }
5120
5121             return this;
5122         },
5123
5124         /**
5125          * Add methods / properties to the prototype of this class.
5126          *
5127          *     Ext.define('My.awesome.Cat', {
5128          *         constructor: function() {
5129          *             ...
5130          *         }
5131          *     });
5132          *
5133          *      My.awesome.Cat.implement({
5134          *          meow: function() {
5135          *             alert('Meowww...');
5136          *          }
5137          *      });
5138          *
5139          *      var kitty = new My.awesome.Cat;
5140          *      kitty.meow();
5141          *
5142          * @param {Object} members
5143          * @static
5144          * @inheritable
5145          */
5146         implement: function(members) {
5147             var prototype = this.prototype,
5148                 enumerables = Ext.enumerables,
5149                 name, i, member;
5150             var className = Ext.getClassName(this);
5151             for (name in members) {
5152                 if (members.hasOwnProperty(name)) {
5153                     member = members[name];
5154
5155                     if (typeof member === 'function') {
5156                         member.$owner = this;
5157                         member.$name = name;
5158                         if (className) {
5159                             member.displayName = className + '#' + name;
5160                         }
5161                     }
5162
5163                     prototype[name] = member;
5164                 }
5165             }
5166
5167             if (enumerables) {
5168                 for (i = enumerables.length; i--;) {
5169                     name = enumerables[i];
5170
5171                     if (members.hasOwnProperty(name)) {
5172                         member = members[name];
5173                         member.$owner = this;
5174                         member.$name = name;
5175                         prototype[name] = member;
5176                     }
5177                 }
5178             }
5179         },
5180
5181         /**
5182          * Borrow another class' members to the prototype of this class.
5183          *
5184          *     Ext.define('Bank', {
5185          *         money: '$$$',
5186          *         printMoney: function() {
5187          *             alert('$$$$$$$');
5188          *         }
5189          *     });
5190          *
5191          *     Ext.define('Thief', {
5192          *         ...
5193          *     });
5194          *
5195          *     Thief.borrow(Bank, ['money', 'printMoney']);
5196          *
5197          *     var steve = new Thief();
5198          *
5199          *     alert(steve.money); // alerts '$$$'
5200          *     steve.printMoney(); // alerts '$$$$$$$'
5201          *
5202          * @param {Ext.Base} fromClass The class to borrow members from
5203          * @param {String/String[]} members The names of the members to borrow
5204          * @return {Ext.Base} this
5205          * @static
5206          * @inheritable
5207          */
5208         borrow: function(fromClass, members) {
5209             var fromPrototype = fromClass.prototype,
5210                 i, ln, member;
5211
5212             members = Ext.Array.from(members);
5213
5214             for (i = 0, ln = members.length; i < ln; i++) {
5215                 member = members[i];
5216
5217                 this.own(member, fromPrototype[member]);
5218             }
5219
5220             return this;
5221         },
5222
5223         /**
5224          * Override prototype members of this class. Overridden methods can be invoked via
5225          * {@link Ext.Base#callOverridden}
5226          *
5227          *     Ext.define('My.Cat', {
5228          *         constructor: function() {
5229          *             alert("I'm a cat!");
5230          *
5231          *             return this;
5232          *         }
5233          *     });
5234          *
5235          *     My.Cat.override({
5236          *         constructor: function() {
5237          *             alert("I'm going to be a cat!");
5238          *
5239          *             var instance = this.callOverridden();
5240          *
5241          *             alert("Meeeeoooowwww");
5242          *
5243          *             return instance;
5244          *         }
5245          *     });
5246          *
5247          *     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
5248          *                               // alerts "I'm a cat!"
5249          *                               // alerts "Meeeeoooowwww"
5250          *
5251          * @param {Object} members
5252          * @return {Ext.Base} this
5253          * @static
5254          * @inheritable
5255          */
5256         override: function(members) {
5257             var prototype = this.prototype,
5258                 enumerables = Ext.enumerables,
5259                 name, i, member, previous;
5260
5261             if (arguments.length === 2) {
5262                 name = members;
5263                 member = arguments[1];
5264
5265                 if (typeof member == 'function') {
5266                     if (typeof prototype[name] == 'function') {
5267                         previous = prototype[name];
5268                         member.$previous = previous;
5269                     }
5270
5271                     this.ownMethod(name, member);
5272                 }
5273                 else {
5274                     prototype[name] = member;
5275                 }
5276
5277                 return this;
5278             }
5279
5280             for (name in members) {
5281                 if (members.hasOwnProperty(name)) {
5282                     member = members[name];
5283
5284                     if (typeof member === 'function') {
5285                         if (typeof prototype[name] === 'function') {
5286                             previous = prototype[name];
5287                             member.$previous = previous;
5288                         }
5289
5290                         this.ownMethod(name, member);
5291                     }
5292                     else {
5293                         prototype[name] = member;
5294                     }
5295                 }
5296             }
5297
5298             if (enumerables) {
5299                 for (i = enumerables.length; i--;) {
5300                     name = enumerables[i];
5301
5302                     if (members.hasOwnProperty(name)) {
5303                         if (typeof prototype[name] !== 'undefined') {
5304                             previous = prototype[name];
5305                             members[name].$previous = previous;
5306                         }
5307
5308                         this.ownMethod(name, members[name]);
5309                     }
5310                 }
5311             }
5312
5313             return this;
5314         },
5315
5316         //<feature classSystem.mixins>
5317         /**
5318          * Used internally by the mixins pre-processor
5319          * @private
5320          * @inheritable
5321          */
5322         mixin: function(name, cls) {
5323             var mixin = cls.prototype,
5324                 my = this.prototype,
5325                 key, fn;
5326
5327             for (key in mixin) {
5328                 if (mixin.hasOwnProperty(key)) {
5329                     if (typeof my[key] === 'undefined' && key !== 'mixins' && key !== 'mixinId') {
5330                         if (typeof mixin[key] === 'function') {
5331                             fn = mixin[key];
5332
5333                             if (typeof fn.$owner === 'undefined') {
5334                                 this.ownMethod(key, fn);
5335                             }
5336                             else {
5337                                 my[key] = fn;
5338                             }
5339                         }
5340                         else {
5341                             my[key] = mixin[key];
5342                         }
5343                     }
5344                     //<feature classSystem.config>
5345                     else if (key === 'config' && my.config && mixin.config) {
5346                         Ext.Object.merge(my.config, mixin.config);
5347                     }
5348                     //</feature>
5349                 }
5350             }
5351
5352             if (typeof mixin.onClassMixedIn !== 'undefined') {
5353                 mixin.onClassMixedIn.call(cls, this);
5354             }
5355
5356             if (!my.hasOwnProperty('mixins')) {
5357                 if ('mixins' in my) {
5358                     my.mixins = Ext.Object.merge({}, my.mixins);
5359                 }
5360                 else {
5361                     my.mixins = {};
5362                 }
5363             }
5364
5365             my.mixins[name] = mixin;
5366         },
5367         //</feature>
5368
5369         /**
5370          * Get the current class' name in string format.
5371          *
5372          *     Ext.define('My.cool.Class', {
5373          *         constructor: function() {
5374          *             alert(this.self.getName()); // alerts 'My.cool.Class'
5375          *         }
5376          *     });
5377          *
5378          *     My.cool.Class.getName(); // 'My.cool.Class'
5379          *
5380          * @return {String} className
5381          * @static
5382          * @inheritable
5383          */
5384         getName: function() {
5385             return Ext.getClassName(this);
5386         },
5387
5388         /**
5389          * Create aliases for existing prototype methods. Example:
5390          *
5391          *     Ext.define('My.cool.Class', {
5392          *         method1: function() { ... },
5393          *         method2: function() { ... }
5394          *     });
5395          *
5396          *     var test = new My.cool.Class();
5397          *
5398          *     My.cool.Class.createAlias({
5399          *         method3: 'method1',
5400          *         method4: 'method2'
5401          *     });
5402          *
5403          *     test.method3(); // test.method1()
5404          *
5405          *     My.cool.Class.createAlias('method5', 'method3');
5406          *
5407          *     test.method5(); // test.method3() -> test.method1()
5408          *
5409          * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
5410          * {@link Ext.Function#flexSetter flexSetter}
5411          * @param {String/Object} origin The original method name
5412          * @static
5413          * @inheritable
5414          * @method
5415          */
5416         createAlias: flexSetter(function(alias, origin) {
5417             this.prototype[alias] = function() {
5418                 return this[origin].apply(this, arguments);
5419             }
5420         })
5421     });
5422
5423 })(Ext.Function.flexSetter);
5424
5425 /**
5426  * @author Jacky Nguyen <jacky@sencha.com>
5427  * @docauthor Jacky Nguyen <jacky@sencha.com>
5428  * @class Ext.Class
5429  *
5430  * Handles class creation throughout the framework. This is a low level factory that is used by Ext.ClassManager and generally
5431  * should not be used directly. If you choose to use Ext.Class you will lose out on the namespace, aliasing and depency loading
5432  * features made available by Ext.ClassManager. The only time you would use Ext.Class directly is to create an anonymous class.
5433  *
5434  * If you wish to create a class you should use {@link Ext#define Ext.define} which aliases
5435  * {@link Ext.ClassManager#create Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution.
5436  *
5437  * Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** Ext classes inherit
5438  * from, see {@link Ext.Base}.
5439  */
5440 (function() {
5441
5442     var Class,
5443         Base = Ext.Base,
5444         baseStaticProperties = [],
5445         baseStaticProperty;
5446
5447     for (baseStaticProperty in Base) {
5448         if (Base.hasOwnProperty(baseStaticProperty)) {
5449             baseStaticProperties.push(baseStaticProperty);
5450         }
5451     }
5452
5453     /**
5454      * @method constructor
5455      * Creates new class.
5456      * @param {Object} classData An object represent the properties of this class
5457      * @param {Function} createdFn (Optional) The callback function to be executed when this class is fully created.
5458      * Note that the creation process can be asynchronous depending on the pre-processors used.
5459      * @return {Ext.Base} The newly created class
5460      */
5461     Ext.Class = Class = function(newClass, classData, onClassCreated) {
5462         if (typeof newClass != 'function') {
5463             onClassCreated = classData;
5464             classData = newClass;
5465             newClass = function() {
5466                 return this.constructor.apply(this, arguments);
5467             };
5468         }
5469
5470         if (!classData) {
5471             classData = {};
5472         }
5473
5474         var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
5475             registeredPreprocessors = Class.getPreprocessors(),
5476             index = 0,
5477             preprocessors = [],
5478             preprocessor, staticPropertyName, process, i, j, ln;
5479
5480         for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
5481             staticPropertyName = baseStaticProperties[i];
5482             newClass[staticPropertyName] = Base[staticPropertyName];
5483         }
5484
5485         delete classData.preprocessors;
5486
5487         for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
5488             preprocessor = preprocessorStack[j];
5489
5490             if (typeof preprocessor == 'string') {
5491                 preprocessor = registeredPreprocessors[preprocessor];
5492
5493                 if (!preprocessor.always) {
5494                     if (classData.hasOwnProperty(preprocessor.name)) {
5495                         preprocessors.push(preprocessor.fn);
5496                     }
5497                 }
5498                 else {
5499                     preprocessors.push(preprocessor.fn);
5500                 }
5501             }
5502             else {
5503                 preprocessors.push(preprocessor);
5504             }
5505         }
5506
5507         classData.onClassCreated = onClassCreated || Ext.emptyFn;
5508
5509         classData.onBeforeClassCreated = function(cls, data) {
5510             onClassCreated = data.onClassCreated;
5511
5512             delete data.onBeforeClassCreated;
5513             delete data.onClassCreated;
5514
5515             cls.implement(data);
5516
5517             onClassCreated.call(cls, cls);
5518         };
5519
5520         process = function(cls, data) {
5521             preprocessor = preprocessors[index++];
5522
5523             if (!preprocessor) {
5524                 data.onBeforeClassCreated.apply(this, arguments);
5525                 return;
5526             }
5527
5528             if (preprocessor.call(this, cls, data, process) !== false) {
5529                 process.apply(this, arguments);
5530             }
5531         };
5532
5533         process.call(Class, newClass, classData);
5534
5535         return newClass;
5536     };
5537
5538     Ext.apply(Class, {
5539
5540         /** @private */
5541         preprocessors: {},
5542
5543         /**
5544          * Register a new pre-processor to be used during the class creation process
5545          *
5546          * @member Ext.Class
5547          * @param {String} name The pre-processor's name
5548          * @param {Function} fn The callback function to be executed. Typical format:
5549          *
5550          *     function(cls, data, fn) {
5551          *         // Your code here
5552          *
5553          *         // Execute this when the processing is finished.
5554          *         // Asynchronous processing is perfectly ok
5555          *         if (fn) {
5556          *             fn.call(this, cls, data);
5557          *         }
5558          *     });
5559          *
5560          * @param {Function} fn.cls The created class
5561          * @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor
5562          * @param {Function} fn.fn The callback function that **must** to be executed when this pre-processor finishes,
5563          * regardless of whether the processing is synchronous or aynchronous
5564          *
5565          * @return {Ext.Class} this
5566          * @static
5567          */
5568         registerPreprocessor: function(name, fn, always) {
5569             this.preprocessors[name] = {
5570                 name: name,
5571                 always: always ||  false,
5572                 fn: fn
5573             };
5574
5575             return this;
5576         },
5577
5578         /**
5579          * Retrieve a pre-processor callback function by its name, which has been registered before
5580          *
5581          * @param {String} name
5582          * @return {Function} preprocessor
5583          * @static
5584          */
5585         getPreprocessor: function(name) {
5586             return this.preprocessors[name];
5587         },
5588
5589         getPreprocessors: function() {
5590             return this.preprocessors;
5591         },
5592
5593         /**
5594          * Retrieve the array stack of default pre-processors
5595          *
5596          * @return {Function[]} defaultPreprocessors
5597          * @static
5598          */
5599         getDefaultPreprocessors: function() {
5600             return this.defaultPreprocessors || [];
5601         },
5602
5603         /**
5604          * Set the default array stack of default pre-processors
5605          *
5606          * @param {Function/Function[]} preprocessors
5607          * @return {Ext.Class} this
5608          * @static
5609          */
5610         setDefaultPreprocessors: function(preprocessors) {
5611             this.defaultPreprocessors = Ext.Array.from(preprocessors);
5612
5613             return this;
5614         },
5615
5616         /**
5617          * Inserts this pre-processor at a specific position in the stack, optionally relative to
5618          * any existing pre-processor. For example:
5619          *
5620          *     Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
5621          *         // Your code here
5622          *
5623          *         if (fn) {
5624          *             fn.call(this, cls, data);
5625          *         }
5626          *     }).setDefaultPreprocessorPosition('debug', 'last');
5627          *
5628          * @param {String} name The pre-processor name. Note that it needs to be registered with
5629          * {@link #registerPreprocessor registerPreprocessor} before this
5630          * @param {String} offset The insertion position. Four possible values are:
5631          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
5632          * @param {String} relativeName
5633          * @return {Ext.Class} this
5634          * @static
5635          */
5636         setDefaultPreprocessorPosition: function(name, offset, relativeName) {
5637             var defaultPreprocessors = this.defaultPreprocessors,
5638                 index;
5639
5640             if (typeof offset == 'string') {
5641                 if (offset === 'first') {
5642                     defaultPreprocessors.unshift(name);
5643
5644                     return this;
5645                 }
5646                 else if (offset === 'last') {
5647                     defaultPreprocessors.push(name);
5648
5649                     return this;
5650                 }
5651
5652                 offset = (offset === 'after') ? 1 : -1;
5653             }
5654
5655             index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
5656
5657             if (index !== -1) {
5658                 Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
5659             }
5660
5661             return this;
5662         }
5663     });
5664
5665     /**
5666      * @cfg {String} extend
5667      * The parent class that this class extends. For example:
5668      *
5669      *     Ext.define('Person', {
5670      *         say: function(text) { alert(text); }
5671      *     });
5672      *
5673      *     Ext.define('Developer', {
5674      *         extend: 'Person',
5675      *         say: function(text) { this.callParent(["print "+text]); }
5676      *     });
5677      */
5678     Class.registerPreprocessor('extend', function(cls, data) {
5679         var extend = data.extend,
5680             base = Ext.Base,
5681             basePrototype = base.prototype,
5682             prototype = function() {},
5683             parent, i, k, ln, staticName, parentStatics,
5684             parentPrototype, clsPrototype;
5685
5686         if (extend && extend !== Object) {
5687             parent = extend;
5688         }
5689         else {
5690             parent = base;
5691         }
5692
5693         parentPrototype = parent.prototype;
5694
5695         prototype.prototype = parentPrototype;
5696         clsPrototype = cls.prototype = new prototype();
5697
5698         if (!('$class' in parent)) {
5699             for (i in basePrototype) {
5700                 if (!parentPrototype[i]) {
5701                     parentPrototype[i] = basePrototype[i];
5702                 }
5703             }
5704         }
5705
5706         clsPrototype.self = cls;
5707
5708         cls.superclass = clsPrototype.superclass = parentPrototype;
5709
5710         delete data.extend;
5711
5712         //<feature classSystem.inheritableStatics>
5713         // Statics inheritance
5714         parentStatics = parentPrototype.$inheritableStatics;
5715
5716         if (parentStatics) {
5717             for (k = 0, ln = parentStatics.length; k < ln; k++) {
5718                 staticName = parentStatics[k];
5719
5720                 if (!cls.hasOwnProperty(staticName)) {
5721                     cls[staticName] = parent[staticName];
5722                 }
5723             }
5724         }
5725         //</feature>
5726
5727         //<feature classSystem.config>
5728         // Merge the parent class' config object without referencing it
5729         if (parentPrototype.config) {
5730             clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
5731         }
5732         else {
5733             clsPrototype.config = {};
5734         }
5735         //</feature>
5736
5737         //<feature classSystem.onClassExtended>
5738         if (clsPrototype.$onExtended) {
5739             clsPrototype.$onExtended.call(cls, cls, data);
5740         }
5741
5742         if (data.onClassExtended) {
5743             clsPrototype.$onExtended = data.onClassExtended;
5744             delete data.onClassExtended;
5745         }
5746         //</feature>
5747
5748     }, true);
5749
5750     //<feature classSystem.statics>
5751     /**
5752      * @cfg {Object} statics
5753      * List of static methods for this class. For example:
5754      *
5755      *     Ext.define('Computer', {
5756      *          statics: {
5757      *              factory: function(brand) {
5758      *                  // 'this' in static methods refer to the class itself
5759      *                  return new this(brand);
5760      *              }
5761      *          },
5762      *
5763      *          constructor: function() { ... }
5764      *     });
5765      *
5766      *     var dellComputer = Computer.factory('Dell');
5767      */
5768     Class.registerPreprocessor('statics', function(cls, data) {
5769         cls.addStatics(data.statics);
5770
5771         delete data.statics;
5772     });
5773     //</feature>
5774
5775     //<feature classSystem.inheritableStatics>
5776     /**
5777      * @cfg {Object} inheritableStatics
5778      * List of inheritable static methods for this class.
5779      * Otherwise just like {@link #statics} but subclasses inherit these methods.
5780      */
5781     Class.registerPreprocessor('inheritableStatics', function(cls, data) {
5782         cls.addInheritableStatics(data.inheritableStatics);
5783
5784         delete data.inheritableStatics;
5785     });
5786     //</feature>
5787
5788     //<feature classSystem.config>
5789     /**
5790      * @cfg {Object} config
5791      * List of configuration options with their default values, for which automatically
5792      * accessor methods are generated.  For example:
5793      *
5794      *     Ext.define('SmartPhone', {
5795      *          config: {
5796      *              hasTouchScreen: false,
5797      *              operatingSystem: 'Other',
5798      *              price: 500
5799      *          },
5800      *          constructor: function(cfg) {
5801      *              this.initConfig(cfg);
5802      *          }
5803      *     });
5804      *
5805      *     var iPhone = new SmartPhone({
5806      *          hasTouchScreen: true,
5807      *          operatingSystem: 'iOS'
5808      *     });
5809      *
5810      *     iPhone.getPrice(); // 500;
5811      *     iPhone.getOperatingSystem(); // 'iOS'
5812      *     iPhone.getHasTouchScreen(); // true;
5813      *     iPhone.hasTouchScreen(); // true
5814      */
5815     Class.registerPreprocessor('config', function(cls, data) {
5816         var prototype = cls.prototype;
5817
5818         Ext.Object.each(data.config, function(name) {
5819             var cName = name.charAt(0).toUpperCase() + name.substr(1),
5820                 pName = name,
5821                 apply = 'apply' + cName,
5822                 setter = 'set' + cName,
5823                 getter = 'get' + cName;
5824
5825             if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
5826                 data[apply] = function(val) {
5827                     return val;
5828                 };
5829             }
5830
5831             if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
5832                 data[setter] = function(val) {
5833                     var ret = this[apply].call(this, val, this[pName]);
5834
5835                     if (typeof ret != 'undefined') {
5836                         this[pName] = ret;
5837                     }
5838
5839                     return this;
5840                 };
5841             }
5842
5843             if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
5844                 data[getter] = function() {
5845                     return this[pName];
5846                 };
5847             }
5848         });
5849
5850         Ext.Object.merge(prototype.config, data.config);
5851         delete data.config;
5852     });
5853     //</feature>
5854
5855     //<feature classSystem.mixins>
5856     /**
5857      * @cfg {Object} mixins
5858      * List of classes to mix into this class. For example:
5859      *
5860      *     Ext.define('CanSing', {
5861      *          sing: function() {
5862      *              alert("I'm on the highway to hell...")
5863      *          }
5864      *     });
5865      *
5866      *     Ext.define('Musician', {
5867      *          extend: 'Person',
5868      *
5869      *          mixins: {
5870      *              canSing: 'CanSing'
5871      *          }
5872      *     })
5873      */
5874     Class.registerPreprocessor('mixins', function(cls, data) {
5875         var mixins = data.mixins,
5876             name, mixin, i, ln;
5877
5878         delete data.mixins;
5879
5880         Ext.Function.interceptBefore(data, 'onClassCreated', function(cls) {
5881             if (mixins instanceof Array) {
5882                 for (i = 0,ln = mixins.length; i < ln; i++) {
5883                     mixin = mixins[i];
5884                     name = mixin.prototype.mixinId || mixin.$className;
5885
5886                     cls.mixin(name, mixin);
5887                 }
5888             }
5889             else {
5890                 for (name in mixins) {
5891                     if (mixins.hasOwnProperty(name)) {
5892                         cls.mixin(name, mixins[name]);
5893                     }
5894                 }
5895             }
5896         });
5897     });
5898
5899     //</feature>
5900
5901     Class.setDefaultPreprocessors([
5902         'extend'
5903         //<feature classSystem.statics>
5904         ,'statics'
5905         //</feature>
5906         //<feature classSystem.inheritableStatics>
5907         ,'inheritableStatics'
5908         //</feature>
5909         //<feature classSystem.config>
5910         ,'config'
5911         //</feature>
5912         //<feature classSystem.mixins>
5913         ,'mixins'
5914         //</feature>
5915     ]);
5916
5917     //<feature classSystem.backwardsCompatible>
5918     // Backwards compatible
5919     Ext.extend = function(subclass, superclass, members) {
5920         if (arguments.length === 2 && Ext.isObject(superclass)) {
5921             members = superclass;
5922             superclass = subclass;
5923             subclass = null;
5924         }
5925
5926         var cls;
5927
5928         if (!superclass) {
5929             Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
5930         }
5931
5932         members.extend = superclass;
5933         members.preprocessors = [
5934             'extend'
5935             //<feature classSystem.statics>
5936             ,'statics'
5937             //</feature>
5938             //<feature classSystem.inheritableStatics>
5939             ,'inheritableStatics'
5940             //</feature>
5941             //<feature classSystem.mixins>
5942             ,'mixins'
5943             //</feature>
5944             //<feature classSystem.config>
5945             ,'config'
5946             //</feature>
5947         ];
5948
5949         if (subclass) {
5950             cls = new Class(subclass, members);
5951         }
5952         else {
5953             cls = new Class(members);
5954         }
5955
5956         cls.prototype.override = function(o) {
5957             for (var m in o) {
5958                 if (o.hasOwnProperty(m)) {
5959                     this[m] = o[m];
5960                 }
5961             }
5962         };
5963
5964         return cls;
5965     };
5966     //</feature>
5967
5968 })();
5969
5970 /**
5971  * @author Jacky Nguyen <jacky@sencha.com>
5972  * @docauthor Jacky Nguyen <jacky@sencha.com>
5973  * @class Ext.ClassManager
5974  *
5975  * Ext.ClassManager manages all classes and handles mapping from string class name to
5976  * actual class objects throughout the whole framework. It is not generally accessed directly, rather through
5977  * these convenient shorthands:
5978  *
5979  * - {@link Ext#define Ext.define}
5980  * - {@link Ext#create Ext.create}
5981  * - {@link Ext#widget Ext.widget}
5982  * - {@link Ext#getClass Ext.getClass}
5983  * - {@link Ext#getClassName Ext.getClassName}
5984  *
5985  * # Basic syntax:
5986  *
5987  *     Ext.define(className, properties);
5988  *
5989  * in which `properties` is an object represent a collection of properties that apply to the class. See
5990  * {@link Ext.ClassManager#create} for more detailed instructions.
5991  *
5992  *     Ext.define('Person', {
5993  *          name: 'Unknown',
5994  *
5995  *          constructor: function(name) {
5996  *              if (name) {
5997  *                  this.name = name;
5998  *              }
5999  *
6000  *              return this;
6001  *          },
6002  *
6003  *          eat: function(foodType) {
6004  *              alert("I'm eating: " + foodType);
6005  *
6006  *              return this;
6007  *          }
6008  *     });
6009  *
6010  *     var aaron = new Person("Aaron");
6011  *     aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
6012  *
6013  * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
6014  * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
6015  *
6016  * # Inheritance:
6017  *
6018  *     Ext.define('Developer', {
6019  *          extend: 'Person',
6020  *
6021  *          constructor: function(name, isGeek) {
6022  *              this.isGeek = isGeek;
6023  *
6024  *              // Apply a method from the parent class' prototype
6025  *              this.callParent([name]);
6026  *
6027  *              return this;
6028  *
6029  *          },
6030  *
6031  *          code: function(language) {
6032  *              alert("I'm coding in: " + language);
6033  *
6034  *              this.eat("Bugs");
6035  *
6036  *              return this;
6037  *          }
6038  *     });
6039  *
6040  *     var jacky = new Developer("Jacky", true);
6041  *     jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
6042  *                               // alert("I'm eating: Bugs");
6043  *
6044  * See {@link Ext.Base#callParent} for more details on calling superclass' methods
6045  *
6046  * # Mixins:
6047  *
6048  *     Ext.define('CanPlayGuitar', {
6049  *          playGuitar: function() {
6050  *             alert("F#...G...D...A");
6051  *          }
6052  *     });
6053  *
6054  *     Ext.define('CanComposeSongs', {
6055  *          composeSongs: function() { ... }
6056  *     });
6057  *
6058  *     Ext.define('CanSing', {
6059  *          sing: function() {
6060  *              alert("I'm on the highway to hell...")
6061  *          }
6062  *     });
6063  *
6064  *     Ext.define('Musician', {
6065  *          extend: 'Person',
6066  *
6067  *          mixins: {
6068  *              canPlayGuitar: 'CanPlayGuitar',
6069  *              canComposeSongs: 'CanComposeSongs',
6070  *              canSing: 'CanSing'
6071  *          }
6072  *     })
6073  *
6074  *     Ext.define('CoolPerson', {
6075  *          extend: 'Person',
6076  *
6077  *          mixins: {
6078  *              canPlayGuitar: 'CanPlayGuitar',
6079  *              canSing: 'CanSing'
6080  *          },
6081  *
6082  *          sing: function() {
6083  *              alert("Ahem....");
6084  *
6085  *              this.mixins.canSing.sing.call(this);
6086  *
6087  *              alert("[Playing guitar at the same time...]");
6088  *
6089  *              this.playGuitar();
6090  *          }
6091  *     });
6092  *
6093  *     var me = new CoolPerson("Jacky");
6094  *
6095  *     me.sing(); // alert("Ahem...");
6096  *                // alert("I'm on the highway to hell...");
6097  *                // alert("[Playing guitar at the same time...]");
6098  *                // alert("F#...G...D...A");
6099  *
6100  * # Config:
6101  *
6102  *     Ext.define('SmartPhone', {
6103  *          config: {
6104  *              hasTouchScreen: false,
6105  *              operatingSystem: 'Other',
6106  *              price: 500
6107  *          },
6108  *
6109  *          isExpensive: false,
6110  *
6111  *          constructor: function(config) {
6112  *              this.initConfig(config);
6113  *
6114  *              return this;
6115  *          },
6116  *
6117  *          applyPrice: function(price) {
6118  *              this.isExpensive = (price > 500);
6119  *
6120  *              return price;
6121  *          },
6122  *
6123  *          applyOperatingSystem: function(operatingSystem) {
6124  *              if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
6125  *                  return 'Other';
6126  *              }
6127  *
6128  *              return operatingSystem;
6129  *          }
6130  *     });
6131  *
6132  *     var iPhone = new SmartPhone({
6133  *          hasTouchScreen: true,
6134  *          operatingSystem: 'iOS'
6135  *     });
6136  *
6137  *     iPhone.getPrice(); // 500;
6138  *     iPhone.getOperatingSystem(); // 'iOS'
6139  *     iPhone.getHasTouchScreen(); // true;
6140  *     iPhone.hasTouchScreen(); // true
6141  *
6142  *     iPhone.isExpensive; // false;
6143  *     iPhone.setPrice(600);
6144  *     iPhone.getPrice(); // 600
6145  *     iPhone.isExpensive; // true;
6146  *
6147  *     iPhone.setOperatingSystem('AlienOS');
6148  *     iPhone.getOperatingSystem(); // 'Other'
6149  *
6150  * # Statics:
6151  *
6152  *     Ext.define('Computer', {
6153  *          statics: {
6154  *              factory: function(brand) {
6155  *                 // 'this' in static methods refer to the class itself
6156  *                  return new this(brand);
6157  *              }
6158  *          },
6159  *
6160  *          constructor: function() { ... }
6161  *     });
6162  *
6163  *     var dellComputer = Computer.factory('Dell');
6164  *
6165  * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
6166  * static properties within class methods
6167  *
6168  * @singleton
6169  */
6170 (function(Class, alias) {
6171
6172     var slice = Array.prototype.slice;
6173
6174     var Manager = Ext.ClassManager = {
6175
6176         /**
6177          * @property {Object} classes
6178          * All classes which were defined through the ClassManager. Keys are the
6179          * name of the classes and the values are references to the classes.
6180          * @private
6181          */
6182         classes: {},
6183
6184         /**
6185          * @private
6186          */
6187         existCache: {},
6188
6189         /**
6190          * @private
6191          */
6192         namespaceRewrites: [{
6193             from: 'Ext.',
6194             to: Ext
6195         }],
6196
6197         /**
6198          * @private
6199          */
6200         maps: {
6201             alternateToName: {},
6202             aliasToName: {},
6203             nameToAliases: {}
6204         },
6205
6206         /** @private */
6207         enableNamespaceParseCache: true,
6208
6209         /** @private */
6210         namespaceParseCache: {},
6211
6212         /** @private */
6213         instantiators: [],
6214
6215         /** @private */
6216         instantiationCounts: {},
6217
6218         /**
6219          * Checks if a class has already been created.
6220          *
6221          * @param {String} className
6222          * @return {Boolean} exist
6223          */
6224         isCreated: function(className) {
6225             var i, ln, part, root, parts;
6226
6227             if (typeof className !== 'string' || className.length < 1) {
6228                 Ext.Error.raise({
6229                     sourceClass: "Ext.ClassManager",
6230                     sourceMethod: "exist",
6231                     msg: "Invalid classname, must be a string and must not be empty"
6232                 });
6233             }
6234
6235             if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
6236                 return true;
6237             }
6238
6239             root = Ext.global;
6240             parts = this.parseNamespace(className);
6241
6242             for (i = 0, ln = parts.length; i < ln; i++) {
6243                 part = parts[i];
6244
6245                 if (typeof part !== 'string') {
6246                     root = part;
6247                 } else {
6248                     if (!root || !root[part]) {
6249                         return false;
6250                     }
6251
6252                     root = root[part];
6253                 }
6254             }
6255
6256             Ext.Loader.historyPush(className);
6257
6258             this.existCache[className] = true;
6259
6260             return true;
6261         },
6262
6263         /**
6264          * Supports namespace rewriting
6265          * @private
6266          */
6267         parseNamespace: function(namespace) {
6268             if (typeof namespace !== 'string') {
6269                 Ext.Error.raise({
6270                     sourceClass: "Ext.ClassManager",
6271                     sourceMethod: "parseNamespace",
6272                     msg: "Invalid namespace, must be a string"
6273                 });
6274             }
6275
6276             var cache = this.namespaceParseCache;
6277
6278             if (this.enableNamespaceParseCache) {
6279                 if (cache.hasOwnProperty(namespace)) {
6280                     return cache[namespace];
6281                 }
6282             }
6283
6284             var parts = [],
6285                 rewrites = this.namespaceRewrites,
6286                 rewrite, from, to, i, ln, root = Ext.global;
6287
6288             for (i = 0, ln = rewrites.length; i < ln; i++) {
6289                 rewrite = rewrites[i];
6290                 from = rewrite.from;
6291                 to = rewrite.to;
6292
6293                 if (namespace === from || namespace.substring(0, from.length) === from) {
6294                     namespace = namespace.substring(from.length);
6295
6296                     if (typeof to !== 'string') {
6297                         root = to;
6298                     } else {
6299                         parts = parts.concat(to.split('.'));
6300                     }
6301
6302                     break;
6303                 }
6304             }
6305
6306             parts.push(root);
6307
6308             parts = parts.concat(namespace.split('.'));
6309
6310             if (this.enableNamespaceParseCache) {
6311                 cache[namespace] = parts;
6312             }
6313
6314             return parts;
6315         },
6316
6317         /**
6318          * Creates a namespace and assign the `value` to the created object
6319          *
6320          *     Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
6321          *
6322          *     alert(MyCompany.pkg.Example === someObject); // alerts true
6323          *
6324          * @param {String} name
6325          * @param {Object} value
6326          */
6327         setNamespace: function(name, value) {
6328             var root = Ext.global,
6329                 parts = this.parseNamespace(name),
6330                 ln = parts.length - 1,
6331                 leaf = parts[ln],
6332                 i, part;
6333
6334             for (i = 0; i < ln; i++) {
6335                 part = parts[i];
6336
6337                 if (typeof part !== 'string') {
6338                     root = part;
6339                 } else {
6340                     if (!root[part]) {
6341                         root[part] = {};
6342                     }
6343
6344                     root = root[part];
6345                 }
6346             }
6347
6348             root[leaf] = value;
6349
6350             return root[leaf];
6351         },
6352
6353         /**
6354          * The new Ext.ns, supports namespace rewriting
6355          * @private
6356          */
6357         createNamespaces: function() {
6358             var root = Ext.global,
6359                 parts, part, i, j, ln, subLn;
6360
6361             for (i = 0, ln = arguments.length; i < ln; i++) {
6362                 parts = this.parseNamespace(arguments[i]);
6363
6364                 for (j = 0, subLn = parts.length; j < subLn; j++) {
6365                     part = parts[j];
6366
6367                     if (typeof part !== 'string') {
6368                         root = part;
6369                     } else {
6370                         if (!root[part]) {
6371                             root[part] = {};
6372                         }
6373
6374                         root = root[part];
6375                     }
6376                 }
6377             }
6378
6379             return root;
6380         },
6381
6382         /**
6383          * Sets a name reference to a class.
6384          *
6385          * @param {String} name
6386          * @param {Object} value
6387          * @return {Ext.ClassManager} this
6388          */
6389         set: function(name, value) {
6390             var targetName = this.getName(value);
6391
6392             this.classes[name] = this.setNamespace(name, value);
6393
6394             if (targetName && targetName !== name) {
6395                 this.maps.alternateToName[name] = targetName;
6396             }
6397
6398             return this;
6399         },
6400
6401         /**
6402          * Retrieve a class by its name.
6403          *
6404          * @param {String} name
6405          * @return {Ext.Class} class
6406          */
6407         get: function(name) {
6408             if (this.classes.hasOwnProperty(name)) {
6409                 return this.classes[name];
6410             }
6411
6412             var root = Ext.global,
6413                 parts = this.parseNamespace(name),
6414                 part, i, ln;
6415
6416             for (i = 0, ln = parts.length; i < ln; i++) {
6417                 part = parts[i];
6418
6419                 if (typeof part !== 'string') {
6420                     root = part;
6421                 } else {
6422                     if (!root || !root[part]) {
6423                         return null;
6424                     }
6425
6426                     root = root[part];
6427                 }
6428             }
6429
6430             return root;
6431         },
6432
6433         /**
6434          * Register the alias for a class.
6435          *
6436          * @param {Ext.Class/String} cls a reference to a class or a className
6437          * @param {String} alias Alias to use when referring to this class
6438          */
6439         setAlias: function(cls, alias) {
6440             var aliasToNameMap = this.maps.aliasToName,
6441                 nameToAliasesMap = this.maps.nameToAliases,
6442                 className;
6443
6444             if (typeof cls === 'string') {
6445                 className = cls;
6446             } else {
6447                 className = this.getName(cls);
6448             }
6449
6450             if (alias && aliasToNameMap[alias] !== className) {
6451                 if (aliasToNameMap.hasOwnProperty(alias) && Ext.isDefined(Ext.global.console)) {
6452                     Ext.global.console.log("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
6453                         "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
6454                 }
6455
6456                 aliasToNameMap[alias] = className;
6457             }
6458
6459             if (!nameToAliasesMap[className]) {
6460                 nameToAliasesMap[className] = [];
6461             }
6462
6463             if (alias) {
6464                 Ext.Array.include(nameToAliasesMap[className], alias);
6465             }
6466
6467             return this;
6468         },
6469
6470         /**
6471          * Get a reference to the class by its alias.
6472          *
6473          * @param {String} alias
6474          * @return {Ext.Class} class
6475          */
6476         getByAlias: function(alias) {
6477             return this.get(this.getNameByAlias(alias));
6478         },
6479
6480         /**
6481          * Get the name of a class by its alias.
6482          *
6483          * @param {String} alias
6484          * @return {String} className
6485          */
6486         getNameByAlias: function(alias) {
6487             return this.maps.aliasToName[alias] || '';
6488         },
6489
6490         /**
6491          * Get the name of a class by its alternate name.
6492          *
6493          * @param {String} alternate
6494          * @return {String} className
6495          */
6496         getNameByAlternate: function(alternate) {
6497             return this.maps.alternateToName[alternate] || '';
6498         },
6499
6500         /**
6501          * Get the aliases of a class by the class name
6502          *
6503          * @param {String} name
6504          * @return {String[]} aliases
6505          */
6506         getAliasesByName: function(name) {
6507             return this.maps.nameToAliases[name] || [];
6508         },
6509
6510         /**
6511          * Get the name of the class by its reference or its instance.
6512          *
6513          *     Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
6514          *
6515          * {@link Ext#getClassName Ext.getClassName} is alias for {@link Ext.ClassManager#getName Ext.ClassManager.getName}.
6516          *
6517          * @param {Ext.Class/Object} object
6518          * @return {String} className
6519          */
6520         getName: function(object) {
6521             return object && object.$className || '';
6522         },
6523
6524         /**
6525          * Get the class of the provided object; returns null if it's not an instance
6526          * of any class created with Ext.define.
6527          *
6528          *     var component = new Ext.Component();
6529          *
6530          *     Ext.ClassManager.getClass(component); // returns Ext.Component
6531          *
6532          * {@link Ext#getClass Ext.getClass} is alias for {@link Ext.ClassManager#getClass Ext.ClassManager.getClass}.
6533          *
6534          * @param {Object} object
6535          * @return {Ext.Class} class
6536          */
6537         getClass: function(object) {
6538             return object && object.self || null;
6539         },
6540
6541         /**
6542          * Defines a class.
6543          *
6544          * {@link Ext#define Ext.define} and {@link Ext.ClassManager#create Ext.ClassManager.create} are almost aliases
6545          * of each other, with the only exception that Ext.define allows definition of {@link Ext.Class#override overrides}.
6546          * To avoid trouble, always use Ext.define.
6547          *
6548          *     Ext.define('My.awesome.Class', {
6549          *         someProperty: 'something',
6550          *         someMethod: function() { ... }
6551          *         ...
6552          *
6553          *     }, function() {
6554          *         alert('Created!');
6555          *         alert(this === My.awesome.Class); // alerts true
6556          *
6557          *         var myInstance = new this();
6558          *     });
6559          *
6560          * @param {String} className The class name to create in string dot-namespaced format, for example:
6561          * `My.very.awesome.Class`, `FeedViewer.plugin.CoolPager`. It is highly recommended to follow this simple convention:
6562          *
6563          * - The root and the class name are 'CamelCased'
6564          * - Everything else is lower-cased
6565          *
6566          * @param {Object} data The key-value pairs of properties to apply to this class. Property names can be of any valid
6567          * strings, except those in the reserved list below:
6568          *
6569          * - {@link Ext.Base#self self}
6570          * - {@link Ext.Class#alias alias}
6571          * - {@link Ext.Class#alternateClassName alternateClassName}
6572          * - {@link Ext.Class#config config}
6573          * - {@link Ext.Class#extend extend}
6574          * - {@link Ext.Class#inheritableStatics inheritableStatics}
6575          * - {@link Ext.Class#mixins mixins}
6576          * - {@link Ext.Class#override override} (only when using {@link Ext#define Ext.define})
6577          * - {@link Ext.Class#requires requires}
6578          * - {@link Ext.Class#singleton singleton}
6579          * - {@link Ext.Class#statics statics}
6580          * - {@link Ext.Class#uses uses}
6581          *
6582          * @param {Function} [createdFn] callback to execute after the class is created, the execution scope of which
6583          * (`this`) will be the newly created class itself.
6584          *
6585          * @return {Ext.Base}
6586          */
6587         create: function(className, data, createdFn) {
6588             var manager = this;
6589
6590             if (typeof className !== 'string') {
6591                 Ext.Error.raise({
6592                     sourceClass: "Ext",
6593                     sourceMethod: "define",
6594                     msg: "Invalid class name '" + className + "' specified, must be a non-empty string"
6595                 });
6596             }
6597
6598             data.$className = className;
6599
6600             return new Class(data, function() {
6601                 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
6602                     registeredPostprocessors = manager.postprocessors,
6603                     index = 0,
6604                     postprocessors = [],
6605                     postprocessor, process, i, ln;
6606
6607                 delete data.postprocessors;
6608
6609                 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
6610                     postprocessor = postprocessorStack[i];
6611
6612                     if (typeof postprocessor === 'string') {
6613                         postprocessor = registeredPostprocessors[postprocessor];
6614
6615                         if (!postprocessor.always) {
6616                             if (data[postprocessor.name] !== undefined) {
6617                                 postprocessors.push(postprocessor.fn);
6618                             }
6619                         }
6620                         else {
6621                             postprocessors.push(postprocessor.fn);
6622                         }
6623                     }
6624                     else {
6625                         postprocessors.push(postprocessor);
6626                     }
6627                 }
6628
6629                 process = function(clsName, cls, clsData) {
6630                     postprocessor = postprocessors[index++];
6631
6632                     if (!postprocessor) {
6633                         manager.set(className, cls);
6634
6635                         Ext.Loader.historyPush(className);
6636
6637                         if (createdFn) {
6638                             createdFn.call(cls, cls);
6639                         }
6640
6641                         return;
6642                     }
6643
6644                     if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
6645                         process.apply(this, arguments);
6646                     }
6647                 };
6648
6649                 process.call(manager, className, this, data);
6650             });
6651         },
6652
6653         /**
6654          * Instantiate a class by its alias.
6655          *
6656          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6657          * attempt to load the class via synchronous loading.
6658          *
6659          *     var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
6660          *
6661          * {@link Ext#createByAlias Ext.createByAlias} is alias for {@link Ext.ClassManager#instantiateByAlias Ext.ClassManager.instantiateByAlias}.
6662          *
6663          * @param {String} alias
6664          * @param {Object...} args Additional arguments after the alias will be passed to the
6665          * class constructor.
6666          * @return {Object} instance
6667          */
6668         instantiateByAlias: function() {
6669             var alias = arguments[0],
6670                 args = slice.call(arguments),
6671                 className = this.getNameByAlias(alias);
6672
6673             if (!className) {
6674                 className = this.maps.aliasToName[alias];
6675
6676                 if (!className) {
6677                     Ext.Error.raise({
6678                         sourceClass: "Ext",
6679                         sourceMethod: "createByAlias",
6680                         msg: "Cannot create an instance of unrecognized alias: " + alias
6681                     });
6682                 }
6683
6684                 if (Ext.global.console) {
6685                     Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
6686                          "Ext.require('" + alias + "') above Ext.onReady");
6687                 }
6688
6689                 Ext.syncRequire(className);
6690             }
6691
6692             args[0] = className;
6693
6694             return this.instantiate.apply(this, args);
6695         },
6696
6697         /**
6698          * Instantiate a class by either full name, alias or alternate name.
6699          *
6700          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6701          * attempt to load the class via synchronous loading.
6702          *
6703          * For example, all these three lines return the same result:
6704          *
6705          *     // alias
6706          *     var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
6707          *
6708          *     // alternate name
6709          *     var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
6710          *
6711          *     // full class name
6712          *     var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
6713          *
6714          * {@link Ext#create Ext.create} is alias for {@link Ext.ClassManager#instantiate Ext.ClassManager.instantiate}.
6715          *
6716          * @param {String} name
6717          * @param {Object...} args Additional arguments after the name will be passed to the class' constructor.
6718          * @return {Object} instance
6719          */
6720         instantiate: function() {
6721             var name = arguments[0],
6722                 args = slice.call(arguments, 1),
6723                 alias = name,
6724                 possibleName, cls;
6725
6726             if (typeof name !== 'function') {
6727                 if ((typeof name !== 'string' || name.length < 1)) {
6728                     Ext.Error.raise({
6729                         sourceClass: "Ext",
6730                         sourceMethod: "create",
6731                         msg: "Invalid class name or alias '" + name + "' specified, must be a non-empty string"
6732                     });
6733                 }
6734
6735                 cls = this.get(name);
6736             }
6737             else {
6738                 cls = name;
6739             }
6740
6741             // No record of this class name, it's possibly an alias, so look it up
6742             if (!cls) {
6743                 possibleName = this.getNameByAlias(name);
6744
6745                 if (possibleName) {
6746                     name = possibleName;
6747
6748                     cls = this.get(name);
6749                 }
6750             }
6751
6752             // Still no record of this class name, it's possibly an alternate name, so look it up
6753             if (!cls) {
6754                 possibleName = this.getNameByAlternate(name);
6755
6756                 if (possibleName) {
6757                     name = possibleName;
6758
6759                     cls = this.get(name);
6760                 }
6761             }
6762
6763             // Still not existing at this point, try to load it via synchronous mode as the last resort
6764             if (!cls) {
6765                 if (Ext.global.console) {
6766                     Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +
6767                          "Ext.require('" + ((possibleName) ? alias : name) + "') above Ext.onReady");
6768                 }
6769
6770                 Ext.syncRequire(name);
6771
6772                 cls = this.get(name);
6773             }
6774
6775             if (!cls) {
6776                 Ext.Error.raise({
6777                     sourceClass: "Ext",
6778                     sourceMethod: "create",
6779                     msg: "Cannot create an instance of unrecognized class name / alias: " + alias
6780                 });
6781             }
6782
6783             if (typeof cls !== 'function') {
6784                 Ext.Error.raise({
6785                     sourceClass: "Ext",
6786                     sourceMethod: "create",
6787                     msg: "'" + name + "' is a singleton and cannot be instantiated"
6788                 });
6789             }
6790
6791             if (!this.instantiationCounts[name]) {
6792                 this.instantiationCounts[name] = 0;
6793             }
6794
6795             this.instantiationCounts[name]++;
6796
6797             return this.getInstantiator(args.length)(cls, args);
6798         },
6799
6800         /**
6801          * @private
6802          * @param name
6803          * @param args
6804          */
6805         dynInstantiate: function(name, args) {
6806             args = Ext.Array.from(args, true);
6807             args.unshift(name);
6808
6809             return this.instantiate.apply(this, args);
6810         },
6811
6812         /**
6813          * @private
6814          * @param length
6815          */
6816         getInstantiator: function(length) {
6817             if (!this.instantiators[length]) {
6818                 var i = length,
6819                     args = [];
6820
6821                 for (i = 0; i < length; i++) {
6822                     args.push('a['+i+']');
6823                 }
6824
6825                 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
6826             }
6827
6828             return this.instantiators[length];
6829         },
6830
6831         /**
6832          * @private
6833          */
6834         postprocessors: {},
6835
6836         /**
6837          * @private
6838          */
6839         defaultPostprocessors: [],
6840
6841         /**
6842          * Register a post-processor function.
6843          *
6844          * @param {String} name
6845          * @param {Function} postprocessor
6846          */
6847         registerPostprocessor: function(name, fn, always) {
6848             this.postprocessors[name] = {
6849                 name: name,
6850                 always: always ||  false,
6851                 fn: fn
6852             };
6853
6854             return this;
6855         },
6856
6857         /**
6858          * Set the default post processors array stack which are applied to every class.
6859          *
6860          * @param {String/String[]} The name of a registered post processor or an array of registered names.
6861          * @return {Ext.ClassManager} this
6862          */
6863         setDefaultPostprocessors: function(postprocessors) {
6864             this.defaultPostprocessors = Ext.Array.from(postprocessors);
6865
6866             return this;
6867         },
6868
6869         /**
6870          * Insert this post-processor at a specific position in the stack, optionally relative to
6871          * any existing post-processor
6872          *
6873          * @param {String} name The post-processor name. Note that it needs to be registered with
6874          * {@link Ext.ClassManager#registerPostprocessor} before this
6875          * @param {String} offset The insertion position. Four possible values are:
6876          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
6877          * @param {String} relativeName
6878          * @return {Ext.ClassManager} this
6879          */
6880         setDefaultPostprocessorPosition: function(name, offset, relativeName) {
6881             var defaultPostprocessors = this.defaultPostprocessors,
6882                 index;
6883
6884             if (typeof offset === 'string') {
6885                 if (offset === 'first') {
6886                     defaultPostprocessors.unshift(name);
6887
6888                     return this;
6889                 }
6890                 else if (offset === 'last') {
6891                     defaultPostprocessors.push(name);
6892
6893                     return this;
6894                 }
6895
6896                 offset = (offset === 'after') ? 1 : -1;
6897             }
6898
6899             index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
6900
6901             if (index !== -1) {
6902                 Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
6903             }
6904
6905             return this;
6906         },
6907
6908         /**
6909          * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
6910          * or class names. Expressions support wildcards:
6911          *
6912          *     // returns ['Ext.window.Window']
6913          *     var window = Ext.ClassManager.getNamesByExpression('widget.window');
6914          *
6915          *     // returns ['widget.panel', 'widget.window', ...]
6916          *     var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
6917          *
6918          *     // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
6919          *     var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
6920          *
6921          * @param {String} expression
6922          * @return {String[]} classNames
6923          */
6924         getNamesByExpression: function(expression) {
6925             var nameToAliasesMap = this.maps.nameToAliases,
6926                 names = [],
6927                 name, alias, aliases, possibleName, regex, i, ln;
6928
6929             if (typeof expression !== 'string' || expression.length < 1) {
6930                 Ext.Error.raise({
6931                     sourceClass: "Ext.ClassManager",
6932                     sourceMethod: "getNamesByExpression",
6933                     msg: "Expression " + expression + " is invalid, must be a non-empty string"
6934                 });
6935             }
6936
6937             if (expression.indexOf('*') !== -1) {
6938                 expression = expression.replace(/\*/g, '(.*?)');
6939                 regex = new RegExp('^' + expression + '$');
6940
6941                 for (name in nameToAliasesMap) {
6942                     if (nameToAliasesMap.hasOwnProperty(name)) {
6943                         aliases = nameToAliasesMap[name];
6944
6945                         if (name.search(regex) !== -1) {
6946                             names.push(name);
6947                         }
6948                         else {
6949                             for (i = 0, ln = aliases.length; i < ln; i++) {
6950                                 alias = aliases[i];
6951
6952                                 if (alias.search(regex) !== -1) {
6953                                     names.push(name);
6954                                     break;
6955                                 }
6956                             }
6957                         }
6958                     }
6959                 }
6960
6961             } else {
6962                 possibleName = this.getNameByAlias(expression);
6963
6964                 if (possibleName) {
6965                     names.push(possibleName);
6966                 } else {
6967                     possibleName = this.getNameByAlternate(expression);
6968
6969                     if (possibleName) {
6970                         names.push(possibleName);
6971                     } else {
6972                         names.push(expression);
6973                     }
6974                 }
6975             }
6976
6977             return names;
6978         }
6979     };
6980
6981     var defaultPostprocessors = Manager.defaultPostprocessors;
6982     //<feature classSystem.alias>
6983
6984     /**
6985      * @cfg {String[]} alias
6986      * @member Ext.Class
6987      * List of short aliases for class names.  Most useful for defining xtypes for widgets:
6988      *
6989      *     Ext.define('MyApp.CoolPanel', {
6990      *         extend: 'Ext.panel.Panel',
6991      *         alias: ['widget.coolpanel'],
6992      *         title: 'Yeah!'
6993      *     });
6994      *
6995      *     // Using Ext.create
6996      *     Ext.widget('widget.coolpanel');
6997      *     // Using the shorthand for widgets and in xtypes
6998      *     Ext.widget('panel', {
6999      *         items: [
7000      *             {xtype: 'coolpanel', html: 'Foo'},
7001      *             {xtype: 'coolpanel', html: 'Bar'}
7002      *         ]
7003      *     });
7004      */
7005     Manager.registerPostprocessor('alias', function(name, cls, data) {
7006         var aliases = data.alias,
7007             i, ln;
7008
7009         delete data.alias;
7010
7011         for (i = 0, ln = aliases.length; i < ln; i++) {
7012             alias = aliases[i];
7013
7014             this.setAlias(cls, alias);
7015         }
7016     });
7017
7018     /**
7019      * @cfg {Boolean} singleton
7020      * @member Ext.Class
7021      * When set to true, the class will be instantiated as singleton.  For example:
7022      *
7023      *     Ext.define('Logger', {
7024      *         singleton: true,
7025      *         log: function(msg) {
7026      *             console.log(msg);
7027      *         }
7028      *     });
7029      *
7030      *     Logger.log('Hello');
7031      */
7032     Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
7033         fn.call(this, name, new cls(), data);
7034         return false;
7035     });
7036
7037     /**
7038      * @cfg {String/String[]} alternateClassName
7039      * @member Ext.Class
7040      * Defines alternate names for this class.  For example:
7041      *
7042      *     Ext.define('Developer', {
7043      *         alternateClassName: ['Coder', 'Hacker'],
7044      *         code: function(msg) {
7045      *             alert('Typing... ' + msg);
7046      *         }
7047      *     });
7048      *
7049      *     var joe = Ext.create('Developer');
7050      *     joe.code('stackoverflow');
7051      *
7052      *     var rms = Ext.create('Hacker');
7053      *     rms.code('hack hack');
7054      */
7055     Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
7056         var alternates = data.alternateClassName,
7057             i, ln, alternate;
7058
7059         if (!(alternates instanceof Array)) {
7060             alternates = [alternates];
7061         }
7062
7063         for (i = 0, ln = alternates.length; i < ln; i++) {
7064             alternate = alternates[i];
7065
7066             if (typeof alternate !== 'string') {
7067                 Ext.Error.raise({
7068                     sourceClass: "Ext",
7069                     sourceMethod: "define",
7070                     msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"
7071                 });
7072             }
7073
7074             this.set(alternate, cls);
7075         }
7076     });
7077
7078     Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
7079
7080     Ext.apply(Ext, {
7081         /**
7082          * @method
7083          * @member Ext
7084          * @alias Ext.ClassManager#instantiate
7085          */
7086         create: alias(Manager, 'instantiate'),
7087
7088         /**
7089          * @private
7090          * API to be stablized
7091          *
7092          * @param {Object} item
7093          * @param {String} namespace
7094          */
7095         factory: function(item, namespace) {
7096             if (item instanceof Array) {
7097                 var i, ln;
7098
7099                 for (i = 0, ln = item.length; i < ln; i++) {
7100                     item[i] = Ext.factory(item[i], namespace);
7101                 }
7102
7103                 return item;
7104             }
7105
7106             var isString = (typeof item === 'string');
7107
7108             if (isString || (item instanceof Object && item.constructor === Object)) {
7109                 var name, config = {};
7110
7111                 if (isString) {
7112                     name = item;
7113                 }
7114                 else {
7115                     name = item.className;
7116                     config = item;
7117                     delete config.className;
7118                 }
7119
7120                 if (namespace !== undefined && name.indexOf(namespace) === -1) {
7121                     name = namespace + '.' + Ext.String.capitalize(name);
7122                 }
7123
7124                 return Ext.create(name, config);
7125             }
7126
7127             if (typeof item === 'function') {
7128                 return Ext.create(item);
7129             }
7130
7131             return item;
7132         },
7133
7134         /**
7135          * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
7136          *
7137          *     var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
7138          *     var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
7139          *
7140          * @method
7141          * @member Ext
7142          * @param {String} name  xtype of the widget to create.
7143          * @param {Object...} args  arguments for the widget constructor.
7144          * @return {Object} widget instance
7145          */
7146         widget: function(name) {
7147             var args = slice.call(arguments);
7148             args[0] = 'widget.' + name;
7149
7150             return Manager.instantiateByAlias.apply(Manager, args);
7151         },
7152
7153         /**
7154          * @method
7155          * @member Ext
7156          * @alias Ext.ClassManager#instantiateByAlias
7157          */
7158         createByAlias: alias(Manager, 'instantiateByAlias'),
7159
7160         /**
7161          * @cfg {String} override
7162          * @member Ext.Class
7163          * 
7164          * Defines an override applied to a class. Note that **overrides can only be created using
7165          * {@link Ext#define}.** {@link Ext.ClassManager#create} only creates classes.
7166          * 
7167          * To define an override, include the override property. The content of an override is
7168          * aggregated with the specified class in order to extend or modify that class. This can be
7169          * as simple as setting default property values or it can extend and/or replace methods.
7170          * This can also extend the statics of the class.
7171          *
7172          * One use for an override is to break a large class into manageable pieces.
7173          *
7174          *      // File: /src/app/Panel.js
7175          *
7176          *      Ext.define('My.app.Panel', {
7177          *          extend: 'Ext.panel.Panel',
7178          *          requires: [
7179          *              'My.app.PanelPart2',
7180          *              'My.app.PanelPart3'
7181          *          ]
7182          *
7183          *          constructor: function (config) {
7184          *              this.callSuper(arguments); // calls Ext.panel.Panel's constructor
7185          *              //...
7186          *          },
7187          *
7188          *          statics: {
7189          *              method: function () {
7190          *                  return 'abc';
7191          *              }
7192          *          }
7193          *      });
7194          *
7195          *      // File: /src/app/PanelPart2.js
7196          *      Ext.define('My.app.PanelPart2', {
7197          *          override: 'My.app.Panel',
7198          *
7199          *          constructor: function (config) {
7200          *              this.callSuper(arguments); // calls My.app.Panel's constructor
7201          *              //...
7202          *          }
7203          *      });
7204          *
7205          * Another use of overrides is to provide optional parts of classes that can be
7206          * independently required. In this case, the class may even be unaware of the
7207          * override altogether.
7208          *
7209          *      Ext.define('My.ux.CoolTip', {
7210          *          override: 'Ext.tip.ToolTip',
7211          *
7212          *          constructor: function (config) {
7213          *              this.callSuper(arguments); // calls Ext.tip.ToolTip's constructor
7214          *              //...
7215          *          }
7216          *      });
7217          *
7218          * The above override can now be required as normal.
7219          *
7220          *      Ext.define('My.app.App', {
7221          *          requires: [
7222          *              'My.ux.CoolTip'
7223          *          ]
7224          *      });
7225          *
7226          * Overrides can also contain statics:
7227          *
7228          *      Ext.define('My.app.BarMod', {
7229          *          override: 'Ext.foo.Bar',
7230          *
7231          *          statics: {
7232          *              method: function (x) {
7233          *                  return this.callSuper([x * 2]); // call Ext.foo.Bar.method
7234          *              }
7235          *          }
7236          *      });
7237          *
7238          * IMPORTANT: An override is only included in a build if the class it overrides is
7239          * required. Otherwise, the override, like the target class, is not included.
7240          */
7241         
7242         /**
7243          * @method
7244          *
7245          * @member Ext
7246          * @alias Ext.ClassManager#create
7247          */
7248         define: function (className, data, createdFn) {
7249             if (!data.override) {
7250                 return Manager.create.apply(Manager, arguments);
7251             }
7252
7253             var requires = data.requires,
7254                 uses = data.uses,
7255                 overrideName = className;
7256
7257             className = data.override;
7258
7259             // hoist any 'requires' or 'uses' from the body onto the faux class:
7260             data = Ext.apply({}, data);
7261             delete data.requires;
7262             delete data.uses;
7263             delete data.override;
7264
7265             // make sure className is in the requires list:
7266             if (typeof requires == 'string') {
7267                 requires = [ className, requires ];
7268             } else if (requires) {
7269                 requires = requires.slice(0);
7270                 requires.unshift(className);
7271             } else {
7272                 requires = [ className ];
7273             }
7274
7275 // TODO - we need to rework this to allow the override to not require the target class
7276 //  and rather 'wait' for it in such a way that if the target class is not in the build,
7277 //  neither are any of its overrides.
7278 //
7279 //  Also, this should process the overrides for a class ASAP (ideally before any derived
7280 //  classes) if the target class 'requires' the overrides. Without some special handling, the
7281 //  overrides so required will be processed before the class and have to be bufferred even
7282 //  in a build.
7283 //
7284 // TODO - we should probably support the "config" processor on an override (to config new
7285 //  functionaliy like Aria) and maybe inheritableStatics (although static is now supported
7286 //  by callSuper). If inheritableStatics causes those statics to be included on derived class
7287 //  constructors, that probably means "no" to this since an override can come after other
7288 //  classes extend the target.
7289             return Manager.create(overrideName, {
7290                     requires: requires,
7291                     uses: uses,
7292                     isPartial: true,
7293                     constructor: function () {
7294                         throw new Error("Cannot create override '" + overrideName + "'");
7295                     }
7296                 }, function () {
7297                     var cls = Manager.get(className);
7298                     if (cls.override) { // if (normal class)
7299                         cls.override(data);
7300                     } else { // else (singleton)
7301                         cls.self.override(data);
7302                     }
7303
7304                     if (createdFn) {
7305                         // called once the override is applied and with the context of the
7306                         // overridden class (the override itself is a meaningless, name-only
7307                         // thing).
7308                         createdFn.call(cls);
7309                     }
7310                 });
7311         },
7312
7313         /**
7314          * @method
7315          * @member Ext
7316          * @alias Ext.ClassManager#getName
7317          */
7318         getClassName: alias(Manager, 'getName'),
7319
7320         /**
7321          * Returns the displayName property or className or object.
7322          * When all else fails, returns "Anonymous".
7323          * @param {Object} object
7324          * @return {String}
7325          */
7326         getDisplayName: function(object) {
7327             if (object.displayName) {
7328                 return object.displayName;
7329             }
7330
7331             if (object.$name && object.$class) {
7332                 return Ext.getClassName(object.$class) + '#' + object.$name;
7333             }
7334
7335             if (object.$className) {
7336                 return object.$className;
7337             }
7338
7339             return 'Anonymous';
7340         },
7341
7342         /**
7343          * @method
7344          * @member Ext
7345          * @alias Ext.ClassManager#getClass
7346          */
7347         getClass: alias(Manager, 'getClass'),
7348
7349         /**
7350          * Creates namespaces to be used for scoping variables and classes so that they are not global.
7351          * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
7352          *
7353          *     Ext.namespace('Company', 'Company.data');
7354          *
7355          *     // equivalent and preferable to the above syntax
7356          *     Ext.namespace('Company.data');
7357          *
7358          *     Company.Widget = function() { ... };
7359          *
7360          *     Company.data.CustomStore = function(config) { ... };
7361          *
7362          * @method
7363          * @member Ext
7364          * @param {String} namespace1
7365          * @param {String} namespace2
7366          * @param {String} etc
7367          * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
7368          */
7369         namespace: alias(Manager, 'createNamespaces')
7370     });
7371
7372     /**
7373      * Old name for {@link Ext#widget}.
7374      * @deprecated 4.0.0 Use {@link Ext#widget} instead.
7375      * @method
7376      * @member Ext
7377      * @alias Ext#widget
7378      */
7379     Ext.createWidget = Ext.widget;
7380
7381     /**
7382      * Convenient alias for {@link Ext#namespace Ext.namespace}
7383      * @method
7384      * @member Ext
7385      * @alias Ext#namespace
7386      */
7387     Ext.ns = Ext.namespace;
7388
7389     Class.registerPreprocessor('className', function(cls, data) {
7390         if (data.$className) {
7391             cls.$className = data.$className;
7392             cls.displayName = cls.$className;
7393         }
7394     }, true);
7395
7396     Class.setDefaultPreprocessorPosition('className', 'first');
7397
7398     Class.registerPreprocessor('xtype', function(cls, data) {
7399         var xtypes = Ext.Array.from(data.xtype),
7400             widgetPrefix = 'widget.',
7401             aliases = Ext.Array.from(data.alias),
7402             i, ln, xtype;
7403
7404         data.xtype = xtypes[0];
7405         data.xtypes = xtypes;
7406
7407         aliases = data.alias = Ext.Array.from(data.alias);
7408
7409         for (i = 0,ln = xtypes.length; i < ln; i++) {
7410             xtype = xtypes[i];
7411
7412             if (typeof xtype != 'string' || xtype.length < 1) {
7413                 throw new Error("[Ext.define] Invalid xtype of: '" + xtype + "' for class: '" + name + "'; must be a valid non-empty string");
7414             }
7415
7416             aliases.push(widgetPrefix + xtype);
7417         }
7418
7419         data.alias = aliases;
7420     });
7421
7422     Class.setDefaultPreprocessorPosition('xtype', 'last');
7423
7424     Class.registerPreprocessor('alias', function(cls, data) {
7425         var aliases = Ext.Array.from(data.alias),
7426             xtypes = Ext.Array.from(data.xtypes),
7427             widgetPrefix = 'widget.',
7428             widgetPrefixLength = widgetPrefix.length,
7429             i, ln, alias, xtype;
7430
7431         for (i = 0, ln = aliases.length; i < ln; i++) {
7432             alias = aliases[i];
7433
7434             if (typeof alias != 'string') {
7435                 throw new Error("[Ext.define] Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string");
7436             }
7437
7438             if (alias.substring(0, widgetPrefixLength) === widgetPrefix) {
7439                 xtype = alias.substring(widgetPrefixLength);
7440                 Ext.Array.include(xtypes, xtype);
7441
7442                 if (!cls.xtype) {
7443                     cls.xtype = data.xtype = xtype;
7444                 }
7445             }
7446         }
7447
7448         data.alias = aliases;
7449         data.xtypes = xtypes;
7450     });
7451
7452     Class.setDefaultPreprocessorPosition('alias', 'last');
7453
7454 })(Ext.Class, Ext.Function.alias);
7455
7456 /**
7457  * @class Ext.Loader
7458  * @singleton
7459  * @author Jacky Nguyen <jacky@sencha.com>
7460  * @docauthor Jacky Nguyen <jacky@sencha.com>
7461  *
7462  * Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
7463  * via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
7464  * approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons
7465  * of each approach:
7466  *
7467  * # Asynchronous Loading
7468  *
7469  * - Advantages:
7470  *       + Cross-domain
7471  *       + No web server needed: you can run the application via the file system protocol
7472  *     (i.e: `file://path/to/your/index.html`)
7473  *       + Best possible debugging experience: error messages come with the exact file name and line number
7474  *
7475  * - Disadvantages:
7476  *       + Dependencies need to be specified before-hand
7477  *
7478  * ### Method 1: Explicitly include what you need:
7479  *
7480  *     // Syntax
7481  *     Ext.require({String/Array} expressions);
7482  *
7483  *     // Example: Single alias
7484  *     Ext.require('widget.window');
7485  *
7486  *     // Example: Single class name
7487  *     Ext.require('Ext.window.Window');
7488  *
7489  *     // Example: Multiple aliases / class names mix
7490  *     Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
7491  *
7492  *     // Wildcards
7493  *     Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
7494  *
7495  * ### Method 2: Explicitly exclude what you don't need:
7496  *
7497  *     // Syntax: Note that it must be in this chaining format.
7498  *     Ext.exclude({String/Array} expressions)
7499  *        .require({String/Array} expressions);
7500  *
7501  *     // Include everything except Ext.data.*
7502  *     Ext.exclude('Ext.data.*').require('*'); 
7503  *
7504  *     // Include all widgets except widget.checkbox*,
7505  *     // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
7506  *     Ext.exclude('widget.checkbox*').require('widget.*');
7507  *
7508  * # Synchronous Loading on Demand
7509  *
7510  * - Advantages:
7511  *       + There's no need to specify dependencies before-hand, which is always the convenience of including
7512  *     ext-all.js before
7513  *
7514  * - Disadvantages:
7515  *       + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
7516  *       + Must be from the same domain due to XHR restriction
7517  *       + Need a web server, same reason as above
7518  *
7519  * There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
7520  *
7521  *     Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
7522  *
7523  *     Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
7524  *
7525  *     Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
7526  *
7527  * Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
7528  * existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load
7529  * the given class and all its dependencies.
7530  *
7531  * # Hybrid Loading - The Best of Both Worlds
7532  *
7533  * It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
7534  *
7535  * ### Step 1: Start writing your application using synchronous approach.
7536  *
7537  * Ext.Loader will automatically fetch all dependencies on demand as they're needed during run-time. For example:
7538  *
7539  *     Ext.onReady(function(){
7540  *         var window = Ext.createWidget('window', {
7541  *             width: 500,
7542  *             height: 300,
7543  *             layout: {
7544  *                 type: 'border',
7545  *                 padding: 5
7546  *             },
7547  *             title: 'Hello Dialog',
7548  *             items: [{
7549  *                 title: 'Navigation',
7550  *                 collapsible: true,
7551  *                 region: 'west',
7552  *                 width: 200,
7553  *                 html: 'Hello',
7554  *                 split: true
7555  *             }, {
7556  *                 title: 'TabPanel',
7557  *                 region: 'center'
7558  *             }]
7559  *         });
7560  *
7561  *         window.show();
7562  *     })
7563  *
7564  * ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these:
7565  *
7566  *     [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code ClassManager.js:432
7567  *     [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
7568  *
7569  * Simply copy and paste the suggested code above `Ext.onReady`, e.g.:
7570  *
7571  *     Ext.require('Ext.window.Window');
7572  *     Ext.require('Ext.layout.container.Border');
7573  *
7574  *     Ext.onReady(...);
7575  *
7576  * Everything should now load via asynchronous mode.
7577  *
7578  * # Deployment
7579  *
7580  * It's important to note that dynamic loading should only be used during development on your local machines.
7581  * During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
7582  * the whole process of transitioning from / to between development / maintenance and production as easy as
7583  * possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies
7584  * your application needs in the exact loading sequence. It's as simple as concatenating all files in this
7585  * array into one, then include it on top of your application.
7586  *
7587  * This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
7588  */
7589 (function(Manager, Class, flexSetter, alias) {
7590
7591     var
7592         dependencyProperties = ['extend', 'mixins', 'requires'],
7593         Loader;
7594
7595     Loader = Ext.Loader = {
7596         /**
7597          * @private
7598          */
7599         documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
7600
7601         /**
7602          * Flag indicating whether there are still files being loaded
7603          * @private
7604          */
7605         isLoading: false,
7606
7607         /**
7608          * Maintain the queue for all dependencies. Each item in the array is an object of the format:
7609          * {
7610          *      requires: [...], // The required classes for this queue item
7611          *      callback: function() { ... } // The function to execute when all classes specified in requires exist
7612          * }
7613          * @private
7614          */
7615         queue: [],
7616
7617         /**
7618          * Maintain the list of files that have already been handled so that they never get double-loaded
7619          * @private
7620          */
7621         isFileLoaded: {},
7622
7623         /**
7624          * Maintain the list of listeners to execute when all required scripts are fully loaded
7625          * @private
7626          */
7627         readyListeners: [],
7628
7629         /**
7630          * Contains optional dependencies to be loaded last
7631          * @private
7632          */
7633         optionalRequires: [],
7634
7635         /**
7636          * Map of fully qualified class names to an array of dependent classes.
7637          * @private
7638          */
7639         requiresMap: {},
7640
7641         /**
7642          * @private
7643          */
7644         numPendingFiles: 0,
7645
7646         /**
7647          * @private
7648          */
7649         numLoadedFiles: 0,
7650
7651         /** @private */
7652         hasFileLoadError: false,
7653
7654         /**
7655          * @private
7656          */
7657         classNameToFilePathMap: {},
7658
7659         /**
7660          * @property {String[]} history
7661          * An array of class names to keep track of the dependency loading order.
7662          * This is not guaranteed to be the same everytime due to the asynchronous nature of the Loader.
7663          */
7664         history: [],
7665
7666         /**
7667          * Configuration
7668          * @private
7669          */
7670         config: {
7671             /**
7672              * @cfg {Boolean} enabled
7673              * Whether or not to enable the dynamic dependency loading feature.
7674              */
7675             enabled: false,
7676
7677             /**
7678              * @cfg {Boolean} disableCaching
7679              * Appends current timestamp to script files to prevent caching.
7680              */
7681             disableCaching: true,
7682
7683             /**
7684              * @cfg {String} disableCachingParam
7685              * The get parameter name for the cache buster's timestamp.
7686              */
7687             disableCachingParam: '_dc',
7688
7689             /**
7690              * @cfg {Object} paths
7691              * The mapping from namespaces to file paths
7692              *
7693              *     {
7694              *         'Ext': '.', // This is set by default, Ext.layout.container.Container will be
7695              *                     // loaded from ./layout/Container.js
7696              *
7697              *         'My': './src/my_own_folder' // My.layout.Container will be loaded from
7698              *                                     // ./src/my_own_folder/layout/Container.js
7699              *     }
7700              *
7701              * Note that all relative paths are relative to the current HTML document.
7702              * If not being specified, for example, `Other.awesome.Class`
7703              * will simply be loaded from `./Other/awesome/Class.js`
7704              */
7705             paths: {
7706                 'Ext': '.'
7707             }
7708         },
7709
7710         /**
7711          * Set the configuration for the loader. This should be called right after ext-core.js
7712          * (or ext-core-debug.js) is included in the page, e.g.:
7713          *
7714          *     <script type="text/javascript" src="ext-core-debug.js"></script>
7715          *     <script type="text/javascript">
7716          *       Ext.Loader.setConfig({
7717          *           enabled: true,
7718          *           paths: {
7719          *               'My': 'my_own_path'
7720          *           }
7721          *       });
7722          *     <script>
7723          *     <script type="text/javascript">
7724          *       Ext.require(...);
7725          *
7726          *       Ext.onReady(function() {
7727          *           // application code here
7728          *       });
7729          *     </script>
7730          *
7731          * Refer to config options of {@link Ext.Loader} for the list of possible properties.
7732          *
7733          * @param {String/Object} name  Name of the value to override, or a config object to override multiple values.
7734          * @param {Object} value  (optional) The new value to set, needed if first parameter is String.
7735          * @return {Ext.Loader} this
7736          */
7737         setConfig: function(name, value) {
7738             if (Ext.isObject(name) && arguments.length === 1) {
7739                 Ext.Object.merge(this.config, name);
7740             }
7741             else {
7742                 this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
7743             }
7744
7745             return this;
7746         },
7747
7748         /**
7749          * Get the config value corresponding to the specified name.
7750          * If no name is given, will return the config object.
7751          * @param {String} name The config property name
7752          * @return {Object}
7753          */
7754         getConfig: function(name) {
7755             if (name) {
7756                 return this.config[name];
7757             }
7758
7759             return this.config;
7760         },
7761
7762         /**
7763          * Sets the path of a namespace. For Example:
7764          *
7765          *     Ext.Loader.setPath('Ext', '.');
7766          *
7767          * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
7768          * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
7769          * @return {Ext.Loader} this
7770          * @method
7771          */
7772         setPath: flexSetter(function(name, path) {
7773             this.config.paths[name] = path;
7774
7775             return this;
7776         }),
7777
7778         /**
7779          * Translates a className to a file path by adding the the proper prefix and converting the .'s to /'s.
7780          * For example:
7781          *
7782          *     Ext.Loader.setPath('My', '/path/to/My');
7783          *
7784          *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
7785          *
7786          * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
7787          *
7788          *     Ext.Loader.setPath({
7789          *         'My': '/path/to/lib',
7790          *         'My.awesome': '/other/path/for/awesome/stuff',
7791          *         'My.awesome.more': '/more/awesome/path'
7792          *     });
7793          *
7794          *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
7795          *
7796          *     alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
7797          *
7798          *     alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
7799          *
7800          *     alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
7801          *
7802          * @param {String} className
7803          * @return {String} path
7804          */
7805         getPath: function(className) {
7806             var path = '',
7807                 paths = this.config.paths,
7808                 prefix = this.getPrefix(className);
7809
7810             if (prefix.length > 0) {
7811                 if (prefix === className) {
7812                     return paths[prefix];
7813                 }
7814
7815                 path = paths[prefix];
7816                 className = className.substring(prefix.length + 1);
7817             }
7818
7819             if (path.length > 0) {
7820                 path += '/';
7821             }
7822
7823             return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
7824         },
7825
7826         /**
7827          * @private
7828          * @param {String} className
7829          */
7830         getPrefix: function(className) {
7831             var paths = this.config.paths,
7832                 prefix, deepestPrefix = '';
7833
7834             if (paths.hasOwnProperty(className)) {
7835                 return className;
7836             }
7837
7838             for (prefix in paths) {
7839                 if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
7840                     if (prefix.length > deepestPrefix.length) {
7841                         deepestPrefix = prefix;
7842                     }
7843                 }
7844             }
7845
7846             return deepestPrefix;
7847         },
7848
7849         /**
7850          * Refresh all items in the queue. If all dependencies for an item exist during looping,
7851          * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
7852          * empty
7853          * @private
7854          */
7855         refreshQueue: function() {
7856             var ln = this.queue.length,
7857                 i, item, j, requires;
7858
7859             if (ln === 0) {
7860                 this.triggerReady();
7861                 return;
7862             }
7863
7864             for (i = 0; i < ln; i++) {
7865                 item = this.queue[i];
7866
7867                 if (item) {
7868                     requires = item.requires;
7869
7870                     // Don't bother checking when the number of files loaded
7871                     // is still less than the array length
7872                     if (requires.length > this.numLoadedFiles) {
7873                         continue;
7874                     }
7875
7876                     j = 0;
7877
7878                     do {
7879                         if (Manager.isCreated(requires[j])) {
7880                             // Take out from the queue
7881                             Ext.Array.erase(requires, j, 1);
7882                         }
7883                         else {
7884                             j++;
7885                         }
7886                     } while (j < requires.length);
7887
7888                     if (item.requires.length === 0) {
7889                         Ext.Array.erase(this.queue, i, 1);
7890                         item.callback.call(item.scope);
7891                         this.refreshQueue();
7892                         break;
7893                     }
7894                 }
7895             }
7896
7897             return this;
7898         },
7899
7900         /**
7901          * Inject a script element to document's head, call onLoad and onError accordingly
7902          * @private
7903          */
7904         injectScriptElement: function(url, onLoad, onError, scope) {
7905             var script = document.createElement('script'),
7906                 me = this,
7907                 onLoadFn = function() {
7908                     me.cleanupScriptElement(script);
7909                     onLoad.call(scope);
7910                 },
7911                 onErrorFn = function() {
7912                     me.cleanupScriptElement(script);
7913                     onError.call(scope);
7914                 };
7915
7916             script.type = 'text/javascript';
7917             script.src = url;
7918             script.onload = onLoadFn;
7919             script.onerror = onErrorFn;
7920             script.onreadystatechange = function() {
7921                 if (this.readyState === 'loaded' || this.readyState === 'complete') {
7922                     onLoadFn();
7923                 }
7924             };
7925
7926             this.documentHead.appendChild(script);
7927
7928             return script;
7929         },
7930
7931         /**
7932          * @private
7933          */
7934         cleanupScriptElement: function(script) {
7935             script.onload = null;
7936             script.onreadystatechange = null;
7937             script.onerror = null;
7938
7939             return this;
7940         },
7941
7942         /**
7943          * Load a script file, supports both asynchronous and synchronous approaches
7944          *
7945          * @param {String} url
7946          * @param {Function} onLoad
7947          * @param {Object} scope
7948          * @param {Boolean} synchronous
7949          * @private
7950          */
7951         loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
7952             var me = this,
7953                 noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
7954                 fileName = url.split('/').pop(),
7955                 isCrossOriginRestricted = false,
7956                 xhr, status, onScriptError;
7957
7958             scope = scope || this;
7959
7960             this.isLoading = true;
7961
7962             if (!synchronous) {
7963                 onScriptError = function() {
7964                     onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
7965                 };
7966
7967                 if (!Ext.isReady && Ext.onDocumentReady) {
7968                     Ext.onDocumentReady(function() {
7969                         me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7970                     });
7971                 }
7972                 else {
7973                     this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7974                 }
7975             }
7976             else {
7977                 if (typeof XMLHttpRequest !== 'undefined') {
7978                     xhr = new XMLHttpRequest();
7979                 } else {
7980                     xhr = new ActiveXObject('Microsoft.XMLHTTP');
7981                 }
7982
7983                 try {
7984                     xhr.open('GET', noCacheUrl, false);
7985                     xhr.send(null);
7986                 } catch (e) {
7987                     isCrossOriginRestricted = true;
7988                 }
7989
7990                 status = (xhr.status === 1223) ? 204 : xhr.status;
7991
7992                 if (!isCrossOriginRestricted) {
7993                     isCrossOriginRestricted = (status === 0);
7994                 }
7995
7996                 if (isCrossOriginRestricted
7997                 ) {
7998                     onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
7999                                        "being loaded from a different domain or from the local file system whereby cross origin " +
8000                                        "requests are not allowed due to security reasons. Use asynchronous loading with " +
8001                                        "Ext.require instead.", synchronous);
8002                 }
8003                 else if (status >= 200 && status < 300
8004                 ) {
8005                     // Firebug friendly, file names are still shown even though they're eval'ed code
8006                     new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();
8007
8008                     onLoad.call(scope);
8009                 }
8010                 else {
8011                     onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
8012                                        "verify that the file exists. " +
8013                                        "XHR status code: " + status, synchronous);
8014                 }
8015
8016                 // Prevent potential IE memory leak
8017                 xhr = null;
8018             }
8019         },
8020
8021         /**
8022          * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
8023          * Can be chained with more `require` and `exclude` methods, e.g.:
8024          *
8025          *     Ext.exclude('Ext.data.*').require('*');
8026          *
8027          *     Ext.exclude('widget.button*').require('widget.*');
8028          *
8029          * {@link Ext#exclude Ext.exclude} is alias for {@link Ext.Loader#exclude Ext.Loader.exclude} for convenience.
8030          *
8031          * @param {String/String[]} excludes
8032          * @return {Object} object contains `require` method for chaining
8033          */
8034         exclude: function(excludes) {
8035             var me = this;
8036
8037             return {
8038                 require: function(expressions, fn, scope) {
8039                     return me.require(expressions, fn, scope, excludes);
8040                 },
8041
8042                 syncRequire: function(expressions, fn, scope) {
8043                     return me.syncRequire(expressions, fn, scope, excludes);
8044                 }
8045             };
8046         },
8047
8048         /**
8049          * Synchronously loads all classes by the given names and all their direct dependencies;
8050          * optionally executes the given callback function when finishes, within the optional scope.
8051          *
8052          * {@link Ext#syncRequire Ext.syncRequire} is alias for {@link Ext.Loader#syncRequire Ext.Loader.syncRequire} for convenience.
8053          *
8054          * @param {String/String[]} expressions Can either be a string or an array of string
8055          * @param {Function} fn (Optional) The callback function
8056          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
8057          * @param {String/String[]} excludes (Optional) Classes to be excluded, useful when being used with expressions
8058          */
8059         syncRequire: function() {
8060             this.syncModeEnabled = true;
8061             this.require.apply(this, arguments);
8062             this.refreshQueue();
8063             this.syncModeEnabled = false;
8064         },
8065
8066         /**
8067          * Loads all classes by the given names and all their direct dependencies;
8068          * optionally executes the given callback function when finishes, within the optional scope.
8069          *
8070          * {@link Ext#require Ext.require} is alias for {@link Ext.Loader#require Ext.Loader.require} for convenience.
8071          *
8072          * @param {String/String[]} expressions Can either be a string or an array of string
8073          * @param {Function} fn (Optional) The callback function
8074          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
8075          * @param {String/String[]} excludes (Optional) Classes to be excluded, useful when being used with expressions
8076          */
8077         require: function(expressions, fn, scope, excludes) {
8078             var filePath, expression, exclude, className, excluded = {},
8079                 excludedClassNames = [],
8080                 possibleClassNames = [],
8081                 possibleClassName, classNames = [],
8082                 i, j, ln, subLn;
8083
8084             expressions = Ext.Array.from(expressions);
8085             excludes = Ext.Array.from(excludes);
8086
8087             fn = fn || Ext.emptyFn;
8088
8089             scope = scope || Ext.global;
8090
8091             for (i = 0, ln = excludes.length; i < ln; i++) {
8092                 exclude = excludes[i];
8093
8094                 if (typeof exclude === 'string' && exclude.length > 0) {
8095                     excludedClassNames = Manager.getNamesByExpression(exclude);
8096
8097                     for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
8098                         excluded[excludedClassNames[j]] = true;
8099                     }
8100                 }
8101             }
8102
8103             for (i = 0, ln = expressions.length; i < ln; i++) {
8104                 expression = expressions[i];
8105
8106                 if (typeof expression === 'string' && expression.length > 0) {
8107                     possibleClassNames = Manager.getNamesByExpression(expression);
8108
8109                     for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
8110                         possibleClassName = possibleClassNames[j];
8111
8112                         if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
8113                             Ext.Array.include(classNames, possibleClassName);
8114                         }
8115                     }
8116                 }
8117             }
8118
8119             // If the dynamic dependency feature is not being used, throw an error
8120             // if the dependencies are not defined
8121             if (!this.config.enabled) {
8122                 if (classNames.length > 0) {
8123                     Ext.Error.raise({
8124                         sourceClass: "Ext.Loader",
8125                         sourceMethod: "require",
8126                         msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
8127                              "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
8128                     });
8129                 }
8130             }
8131
8132             if (classNames.length === 0) {
8133                 fn.call(scope);
8134                 return this;
8135             }
8136
8137             this.queue.push({
8138                 requires: classNames,
8139                 callback: fn,
8140                 scope: scope
8141             });
8142
8143             classNames = classNames.slice();
8144
8145             for (i = 0, ln = classNames.length; i < ln; i++) {
8146                 className = classNames[i];
8147
8148                 if (!this.isFileLoaded.hasOwnProperty(className)) {
8149                     this.isFileLoaded[className] = false;
8150
8151                     filePath = this.getPath(className);
8152
8153                     this.classNameToFilePathMap[className] = filePath;
8154
8155                     this.numPendingFiles++;
8156
8157                     this.loadScriptFile(
8158                         filePath,
8159                         Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
8160                         Ext.Function.pass(this.onFileLoadError, [className, filePath]),
8161                         this,
8162                         this.syncModeEnabled
8163                     );
8164                 }
8165             }
8166
8167             return this;
8168         },
8169
8170         /**
8171          * @private
8172          * @param {String} className
8173          * @param {String} filePath
8174          */
8175         onFileLoaded: function(className, filePath) {
8176             this.numLoadedFiles++;
8177
8178             this.isFileLoaded[className] = true;
8179
8180             this.numPendingFiles--;
8181
8182             if (this.numPendingFiles === 0) {
8183                 this.refreshQueue();
8184             }
8185
8186             if (this.numPendingFiles <= 1) {
8187                 window.status = "Finished loading all dependencies, onReady fired!";
8188             }
8189             else {
8190                 window.status = "Loading dependencies, " + this.numPendingFiles + " files left...";
8191             }
8192
8193             if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
8194                 var queue = this.queue,
8195                     requires,
8196                     i, ln, j, subLn, missingClasses = [], missingPaths = [];
8197
8198                 for (i = 0, ln = queue.length; i < ln; i++) {
8199                     requires = queue[i].requires;
8200
8201                     for (j = 0, subLn = requires.length; j < ln; j++) {
8202                         if (this.isFileLoaded[requires[j]]) {
8203                             missingClasses.push(requires[j]);
8204                         }
8205                     }
8206                 }
8207
8208                 if (missingClasses.length < 1) {
8209                     return;
8210                 }
8211
8212                 missingClasses = Ext.Array.filter(missingClasses, function(item) {
8213                     return !this.requiresMap.hasOwnProperty(item);
8214                 }, this);
8215
8216                 for (i = 0,ln = missingClasses.length; i < ln; i++) {
8217                     missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
8218                 }
8219
8220                 Ext.Error.raise({
8221                     sourceClass: "Ext.Loader",
8222                     sourceMethod: "onFileLoaded",
8223                     msg: "The following classes are not declared even if their files have been " +
8224                             "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
8225                             "corresponding files for possible typos: '" + missingPaths.join("', '") + "'"
8226                 });
8227             }
8228         },
8229
8230         /**
8231          * @private
8232          */
8233         onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
8234             this.numPendingFiles--;
8235             this.hasFileLoadError = true;
8236
8237             Ext.Error.raise({
8238                 sourceClass: "Ext.Loader",
8239                 classToLoad: className,
8240                 loadPath: filePath,
8241                 loadingType: isSynchronous ? 'synchronous' : 'async',
8242                 msg: errorMessage
8243             });
8244         },
8245
8246         /**
8247          * @private
8248          */
8249         addOptionalRequires: function(requires) {
8250             var optionalRequires = this.optionalRequires,
8251                 i, ln, require;
8252
8253             requires = Ext.Array.from(requires);
8254
8255             for (i = 0, ln = requires.length; i < ln; i++) {
8256                 require = requires[i];
8257
8258                 Ext.Array.include(optionalRequires, require);
8259             }
8260
8261             return this;
8262         },
8263
8264         /**
8265          * @private
8266          */
8267         triggerReady: function(force) {
8268             var readyListeners = this.readyListeners,
8269                 optionalRequires, listener;
8270
8271             if (this.isLoading || force) {
8272                 this.isLoading = false;
8273
8274                 if (this.optionalRequires.length) {
8275                     // Clone then empty the array to eliminate potential recursive loop issue
8276                     optionalRequires = Ext.Array.clone(this.optionalRequires);
8277
8278                     // Empty the original array
8279                     this.optionalRequires.length = 0;
8280
8281                     this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
8282                     return this;
8283                 }
8284
8285                 while (readyListeners.length) {
8286                     listener = readyListeners.shift();
8287                     listener.fn.call(listener.scope);
8288
8289                     if (this.isLoading) {
8290                         return this;
8291                     }
8292                 }
8293             }
8294
8295             return this;
8296         },
8297
8298         /**
8299          * Adds new listener to be executed when all required scripts are fully loaded.
8300          *
8301          * @param {Function} fn The function callback to be executed
8302          * @param {Object} scope The execution scope (`this`) of the callback function
8303          * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
8304          */
8305         onReady: function(fn, scope, withDomReady, options) {
8306             var oldFn;
8307
8308             if (withDomReady !== false && Ext.onDocumentReady) {
8309                 oldFn = fn;
8310
8311                 fn = function() {
8312                     Ext.onDocumentReady(oldFn, scope, options);
8313                 };
8314             }
8315
8316             if (!this.isLoading) {
8317                 fn.call(scope);
8318             }
8319             else {
8320                 this.readyListeners.push({
8321                     fn: fn,
8322                     scope: scope
8323                 });
8324             }
8325         },
8326
8327         /**
8328          * @private
8329          * @param {String} className
8330          */
8331         historyPush: function(className) {
8332             if (className && this.isFileLoaded.hasOwnProperty(className)) {
8333                 Ext.Array.include(this.history, className);
8334             }
8335
8336             return this;
8337         }
8338     };
8339
8340     /**
8341      * @member Ext
8342      * @method require
8343      * @alias Ext.Loader#require
8344      */
8345     Ext.require = alias(Loader, 'require');
8346
8347     /**
8348      * @member Ext
8349      * @method syncRequire
8350      * @alias Ext.Loader#syncRequire
8351      */
8352     Ext.syncRequire = alias(Loader, 'syncRequire');
8353
8354     /**
8355      * @member Ext
8356      * @method exclude
8357      * @alias Ext.Loader#exclude
8358      */
8359     Ext.exclude = alias(Loader, 'exclude');
8360
8361     /**
8362      * @member Ext
8363      * @method onReady
8364      * @alias Ext.Loader#onReady
8365      */
8366     Ext.onReady = function(fn, scope, options) {
8367         Loader.onReady(fn, scope, true, options);
8368     };
8369
8370     /**
8371      * @cfg {String[]} requires
8372      * @member Ext.Class
8373      * List of classes that have to be loaded before instantiating this class.
8374      * For example:
8375      *
8376      *     Ext.define('Mother', {
8377      *         requires: ['Child'],
8378      *         giveBirth: function() {
8379      *             // we can be sure that child class is available.
8380      *             return new Child();
8381      *         }
8382      *     });
8383      */
8384     Class.registerPreprocessor('loader', function(cls, data, continueFn) {
8385         var me = this,
8386             dependencies = [],
8387             className = Manager.getName(cls),
8388             i, j, ln, subLn, value, propertyName, propertyValue;
8389
8390         /*
8391         Basically loop through the dependencyProperties, look for string class names and push
8392         them into a stack, regardless of whether the property's value is a string, array or object. For example:
8393         {
8394               extend: 'Ext.MyClass',
8395               requires: ['Ext.some.OtherClass'],
8396               mixins: {
8397                   observable: 'Ext.util.Observable';
8398               }
8399         }
8400         which will later be transformed into:
8401         {
8402               extend: Ext.MyClass,
8403               requires: [Ext.some.OtherClass],
8404               mixins: {
8405                   observable: Ext.util.Observable;
8406               }
8407         }
8408         */
8409
8410         for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
8411             propertyName = dependencyProperties[i];
8412
8413             if (data.hasOwnProperty(propertyName)) {
8414                 propertyValue = data[propertyName];
8415
8416                 if (typeof propertyValue === 'string') {
8417                     dependencies.push(propertyValue);
8418                 }
8419                 else if (propertyValue instanceof Array) {
8420                     for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
8421                         value = propertyValue[j];
8422
8423                         if (typeof value === 'string') {
8424                             dependencies.push(value);
8425                         }
8426                     }
8427                 }
8428                 else if (typeof propertyValue != 'function') {
8429                     for (j in propertyValue) {
8430                         if (propertyValue.hasOwnProperty(j)) {
8431                             value = propertyValue[j];
8432
8433                             if (typeof value === 'string') {
8434                                 dependencies.push(value);
8435                             }
8436                         }
8437                     }
8438                 }
8439             }
8440         }
8441
8442         if (dependencies.length === 0) {
8443 //            Loader.historyPush(className);
8444             return;
8445         }
8446
8447         var deadlockPath = [],
8448             requiresMap = Loader.requiresMap,
8449             detectDeadlock;
8450
8451         /*
8452         Automatically detect deadlocks before-hand,
8453         will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
8454
8455         - A extends B, then B extends A
8456         - A requires B, B requires C, then C requires A
8457
8458         The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
8459         no matter how deep the path is.
8460         */
8461
8462         if (className) {
8463             requiresMap[className] = dependencies;
8464
8465             detectDeadlock = function(cls) {
8466                 deadlockPath.push(cls);
8467
8468                 if (requiresMap[cls]) {
8469                     if (Ext.Array.contains(requiresMap[cls], className)) {
8470                         Ext.Error.raise({
8471                             sourceClass: "Ext.Loader",
8472                             msg: "Deadlock detected while loading dependencies! '" + className + "' and '" +
8473                                 deadlockPath[1] + "' " + "mutually require each other. Path: " +
8474                                 deadlockPath.join(' -> ') + " -> " + deadlockPath[0]
8475                         });
8476                     }
8477
8478                     for (i = 0, ln = requiresMap[cls].length; i < ln; i++) {
8479                         detectDeadlock(requiresMap[cls][i]);
8480                     }
8481                 }
8482             };
8483
8484             detectDeadlock(className);
8485         }
8486
8487
8488         Loader.require(dependencies, function() {
8489             for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
8490                 propertyName = dependencyProperties[i];
8491
8492                 if (data.hasOwnProperty(propertyName)) {
8493                     propertyValue = data[propertyName];
8494
8495                     if (typeof propertyValue === 'string') {
8496                         data[propertyName] = Manager.get(propertyValue);
8497                     }
8498                     else if (propertyValue instanceof Array) {
8499                         for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
8500                             value = propertyValue[j];
8501
8502                             if (typeof value === 'string') {
8503                                 data[propertyName][j] = Manager.get(value);
8504                             }
8505                         }
8506                     }
8507                     else if (typeof propertyValue != 'function') {
8508                         for (var k in propertyValue) {
8509                             if (propertyValue.hasOwnProperty(k)) {
8510                                 value = propertyValue[k];
8511
8512                                 if (typeof value === 'string') {
8513                                     data[propertyName][k] = Manager.get(value);
8514                                 }
8515                             }
8516                         }
8517                     }
8518                 }
8519             }
8520
8521             continueFn.call(me, cls, data);
8522         });
8523
8524         return false;
8525     }, true);
8526
8527     Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
8528
8529     /**
8530      * @cfg {String[]} uses
8531      * @member Ext.Class
8532      * List of classes to load together with this class.  These aren't neccessarily loaded before
8533      * this class is instantiated. For example:
8534      *
8535      *     Ext.define('Mother', {
8536      *         uses: ['Child'],
8537      *         giveBirth: function() {
8538      *             // This code might, or might not work:
8539      *             // return new Child();
8540      *
8541      *             // Instead use Ext.create() to load the class at the spot if not loaded already:
8542      *             return Ext.create('Child');
8543      *         }
8544      *     });
8545      */
8546     Manager.registerPostprocessor('uses', function(name, cls, data) {
8547         var uses = Ext.Array.from(data.uses),
8548             items = [],
8549             i, ln, item;
8550
8551         for (i = 0, ln = uses.length; i < ln; i++) {
8552             item = uses[i];
8553
8554             if (typeof item === 'string') {
8555                 items.push(item);
8556             }
8557         }
8558
8559         Loader.addOptionalRequires(items);
8560     });
8561
8562     Manager.setDefaultPostprocessorPosition('uses', 'last');
8563
8564 })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
8565
8566 /**
8567  * @author Brian Moeskau <brian@sencha.com>
8568  * @docauthor Brian Moeskau <brian@sencha.com>
8569  *
8570  * A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
8571  * errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
8572  * uses the Ext 4 class system, the Error class can automatically add the source class and method from which
8573  * the error was raised. It also includes logic to automatically log the eroor to the console, if available,
8574  * with additional metadata about the error. In all cases, the error will always be thrown at the end so that
8575  * execution will halt.
8576  *
8577  * Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
8578  * handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
8579  * although in a real application it's usually a better idea to override the handling function and perform
8580  * logging or some other method of reporting the errors in a way that is meaningful to the application.
8581  *
8582  * At its simplest you can simply raise an error as a simple string from within any code:
8583  *
8584  * Example usage:
8585  *
8586  *     Ext.Error.raise('Something bad happened!');
8587  *
8588  * If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
8589  * displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
8590  * additional metadata about the error being raised.  The {@link #raise} method can also take a config object.
8591  * In this form the `msg` attribute becomes the error description, and any other data added to the config gets
8592  * added to the error object and, if the console is available, logged to the console for inspection.
8593  *
8594  * Example usage:
8595  *
8596  *     Ext.define('Ext.Foo', {
8597  *         doSomething: function(option){
8598  *             if (someCondition === false) {
8599  *                 Ext.Error.raise({
8600  *                     msg: 'You cannot do that!',
8601  *                     option: option,   // whatever was passed into the method
8602  *                     'error code': 100 // other arbitrary info
8603  *                 });
8604  *             }
8605  *         }
8606  *     });
8607  *
8608  * If a console is available (that supports the `console.dir` function) you'll see console output like:
8609  *
8610  *     An error was raised with the following data:
8611  *     option:         Object { foo: "bar"}
8612  *         foo:        "bar"
8613  *     error code:     100
8614  *     msg:            "You cannot do that!"
8615  *     sourceClass:   "Ext.Foo"
8616  *     sourceMethod:  "doSomething"
8617  *
8618  *     uncaught exception: You cannot do that!
8619  *
8620  * As you can see, the error will report exactly where it was raised and will include as much information as the
8621  * raising code can usefully provide.
8622  *
8623  * If you want to handle all application errors globally you can simply override the static {@link #handle} method
8624  * and provide whatever handling logic you need. If the method returns true then the error is considered handled
8625  * and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
8626  *
8627  * Example usage:
8628  *
8629  *     Ext.Error.handle = function(err) {
8630  *         if (err.someProperty == 'NotReallyAnError') {
8631  *             // maybe log something to the application here if applicable
8632  *             return true;
8633  *         }
8634  *         // any non-true return value (including none) will cause the error to be thrown
8635  *     }
8636  *
8637  */
8638 Ext.Error = Ext.extend(Error, {
8639     statics: {
8640         /**
8641          * @property {Boolean} ignore
8642          * Static flag that can be used to globally disable error reporting to the browser if set to true
8643          * (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
8644          * and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
8645          * be preferable to supply a custom error {@link #handle handling} function instead.
8646          *
8647          * Example usage:
8648          *
8649          *     Ext.Error.ignore = true;
8650          *
8651          * @static
8652          */
8653         ignore: false,
8654
8655         /**
8656          * @property {Boolean} notify
8657          * Static flag that can be used to globally control error notification to the user. Unlike
8658          * Ex.Error.ignore, this does not effect exceptions. They are still thrown. This value can be
8659          * set to false to disable the alert notification (default is true for IE6 and IE7).
8660          *
8661          * Only the first error will generate an alert. Internally this flag is set to false when the
8662          * first error occurs prior to displaying the alert.
8663          *
8664          * This flag is not used in a release build.
8665          *
8666          * Example usage:
8667          *
8668          *     Ext.Error.notify = false;
8669          *
8670          * @static
8671          */
8672         //notify: Ext.isIE6 || Ext.isIE7,
8673
8674         /**
8675          * Raise an error that can include additional data and supports automatic console logging if available.
8676          * You can pass a string error message or an object with the `msg` attribute which will be used as the
8677          * error message. The object can contain any other name-value attributes (or objects) to be logged
8678          * along with the error.
8679          *
8680          * Note that after displaying the error message a JavaScript error will ultimately be thrown so that
8681          * execution will halt.
8682          *
8683          * Example usage:
8684          *
8685          *     Ext.Error.raise('A simple string error message');
8686          *
8687          *     // or...
8688          *
8689          *     Ext.define('Ext.Foo', {
8690          *         doSomething: function(option){
8691          *             if (someCondition === false) {
8692          *                 Ext.Error.raise({
8693          *                     msg: 'You cannot do that!',
8694          *                     option: option,   // whatever was passed into the method
8695          *                     'error code': 100 // other arbitrary info
8696          *                 });
8697          *             }
8698          *         }
8699          *     });
8700          *
8701          * @param {String/Object} err The error message string, or an object containing the attribute "msg" that will be
8702          * used as the error message. Any other data included in the object will also be logged to the browser console,
8703          * if available.
8704          * @static
8705          */
8706         raise: function(err){
8707             err = err || {};
8708             if (Ext.isString(err)) {
8709                 err = { msg: err };
8710             }
8711
8712             var method = this.raise.caller;
8713
8714             if (method) {
8715                 if (method.$name) {
8716                     err.sourceMethod = method.$name;
8717                 }
8718                 if (method.$owner) {
8719                     err.sourceClass = method.$owner.$className;
8720                 }
8721             }
8722
8723             if (Ext.Error.handle(err) !== true) {
8724                 var msg = Ext.Error.prototype.toString.call(err);
8725
8726                 Ext.log({
8727                     msg: msg,
8728                     level: 'error',
8729                     dump: err,
8730                     stack: true
8731                 });
8732
8733                 throw new Ext.Error(err);
8734             }
8735         },
8736
8737         /**
8738          * Globally handle any Ext errors that may be raised, optionally providing custom logic to
8739          * handle different errors individually. Return true from the function to bypass throwing the
8740          * error to the browser, otherwise the error will be thrown and execution will halt.
8741          *
8742          * Example usage:
8743          *
8744          *     Ext.Error.handle = function(err) {
8745          *         if (err.someProperty == 'NotReallyAnError') {
8746          *             // maybe log something to the application here if applicable
8747          *             return true;
8748          *         }
8749          *         // any non-true return value (including none) will cause the error to be thrown
8750          *     }
8751          *
8752          * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes that were originally
8753          * raised with it, plus properties about the method and class from which the error originated (if raised from a
8754          * class that uses the Ext 4 class system).
8755          * @static
8756          */
8757         handle: function(){
8758             return Ext.Error.ignore;
8759         }
8760     },
8761
8762     // This is the standard property that is the name of the constructor.
8763     name: 'Ext.Error',
8764
8765     /**
8766      * Creates new Error object.
8767      * @param {String/Object} config The error message string, or an object containing the
8768      * attribute "msg" that will be used as the error message. Any other data included in
8769      * the object will be applied to the error instance and logged to the browser console, if available.
8770      */
8771     constructor: function(config){
8772         if (Ext.isString(config)) {
8773             config = { msg: config };
8774         }
8775
8776         var me = this;
8777
8778         Ext.apply(me, config);
8779
8780         me.message = me.message || me.msg; // 'message' is standard ('msg' is non-standard)
8781         // note: the above does not work in old WebKit (me.message is readonly) (Safari 4)
8782     },
8783
8784     /**
8785      * Provides a custom string representation of the error object. This is an override of the base JavaScript
8786      * `Object.toString` method, which is useful so that when logged to the browser console, an error object will
8787      * be displayed with a useful message instead of `[object Object]`, the default `toString` result.
8788      *
8789      * The default implementation will include the error message along with the raising class and method, if available,
8790      * but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
8791      * a particular error instance, if you want to provide a custom description that will show up in the console.
8792      * @return {String} The error message. If raised from within the Ext 4 class system, the error message will also
8793      * include the raising class and method names, if available.
8794      */
8795     toString: function(){
8796         var me = this,
8797             className = me.className ? me.className  : '',
8798             methodName = me.methodName ? '.' + me.methodName + '(): ' : '',
8799             msg = me.msg || '(No description provided)';
8800
8801         return className + methodName + msg;
8802     }
8803 });
8804
8805 /*
8806  * This mechanism is used to notify the user of the first error encountered on the page. This
8807  * was previously internal to Ext.Error.raise and is a desirable feature since errors often
8808  * slip silently under the radar. It cannot live in Ext.Error.raise since there are times
8809  * where exceptions are handled in a try/catch.
8810  */
8811 (function () {
8812     var prevOnError, timer, errors = 0,
8813         extraordinarilyBad = /(out of stack)|(too much recursion)|(stack overflow)|(out of memory)/i,
8814         win = Ext.global;
8815
8816     if (typeof window === 'undefined') {
8817         return; // build system or some such environment...
8818     }
8819
8820     // This method is called to notify the user of the current error status.
8821     function notify () {
8822         var counters = Ext.log.counters,
8823             supports = Ext.supports,
8824             hasOnError = supports && supports.WindowOnError; // TODO - timing
8825
8826         // Put log counters to the status bar (for most browsers):
8827         if (counters && (counters.error + counters.warn + counters.info + counters.log)) {
8828             var msg = [ 'Logged Errors:',counters.error, 'Warnings:',counters.warn,
8829                         'Info:',counters.info, 'Log:',counters.log].join(' ');
8830             if (errors) {
8831                 msg = '*** Errors: ' + errors + ' - ' + msg;
8832             } else if (counters.error) {
8833                 msg = '*** ' + msg;
8834             }
8835             win.status = msg;
8836         }
8837
8838         // Display an alert on the first error:
8839         if (!Ext.isDefined(Ext.Error.notify)) {
8840             Ext.Error.notify = Ext.isIE6 || Ext.isIE7; // TODO - timing
8841         }
8842         if (Ext.Error.notify && (hasOnError ? errors : (counters && counters.error))) {
8843             Ext.Error.notify = false;
8844
8845             if (timer) {
8846                 win.clearInterval(timer); // ticks can queue up so stop...
8847                 timer = null;
8848             }
8849
8850             alert('Unhandled error on page: See console or log');
8851             poll();
8852         }
8853     }
8854
8855     // Sets up polling loop. This is the only way to know about errors in some browsers
8856     // (Opera/Safari) and is the only way to update the status bar for warnings and other
8857     // non-errors.
8858     function poll () {
8859         timer = win.setInterval(notify, 1000);
8860     }
8861
8862     // window.onerror sounds ideal but it prevents the built-in error dialog from doing
8863     // its (better) thing.
8864     poll();
8865 })();
8866
8867
8868
8869 /*
8870
8871 This file is part of Ext JS 4
8872
8873 Copyright (c) 2011 Sencha Inc
8874
8875 Contact:  http://www.sencha.com/contact
8876
8877 GNU General Public License Usage
8878 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.
8879
8880 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
8881
8882 */
8883 /**
8884  * @class Ext.JSON
8885  * Modified version of Douglas Crockford's JSON.js that doesn't
8886  * mess with the Object prototype
8887  * http://www.json.org/js.html
8888  * @singleton
8889  */
8890 Ext.JSON = new(function() {
8891     var useHasOwn = !! {}.hasOwnProperty,
8892     isNative = function() {
8893         var useNative = null;
8894
8895         return function() {
8896             if (useNative === null) {
8897                 useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
8898             }
8899
8900             return useNative;
8901         };
8902     }(),
8903     pad = function(n) {
8904         return n < 10 ? "0" + n : n;
8905     },
8906     doDecode = function(json) {
8907         return eval("(" + json + ')');
8908     },
8909     doEncode = function(o) {
8910         if (!Ext.isDefined(o) || o === null) {
8911             return "null";
8912         } else if (Ext.isArray(o)) {
8913             return encodeArray(o);
8914         } else if (Ext.isDate(o)) {
8915             return Ext.JSON.encodeDate(o);
8916         } else if (Ext.isString(o)) {
8917             return encodeString(o);
8918         } else if (typeof o == "number") {
8919             //don't use isNumber here, since finite checks happen inside isNumber
8920             return isFinite(o) ? String(o) : "null";
8921         } else if (Ext.isBoolean(o)) {
8922             return String(o);
8923         } else if (Ext.isObject(o)) {
8924             return encodeObject(o);
8925         } else if (typeof o === "function") {
8926             return "null";
8927         }
8928         return 'undefined';
8929     },
8930     m = {
8931         "\b": '\\b',
8932         "\t": '\\t',
8933         "\n": '\\n',
8934         "\f": '\\f',
8935         "\r": '\\r',
8936         '"': '\\"',
8937         "\\": '\\\\',
8938         '\x0b': '\\u000b' //ie doesn't handle \v
8939     },
8940     charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g,
8941     encodeString = function(s) {
8942         return '"' + s.replace(charToReplace, function(a) {
8943             var c = m[a];
8944             return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
8945         }) + '"';
8946     },
8947     encodeArray = function(o) {
8948         var a = ["[", ""],
8949         // Note empty string in case there are no serializable members.
8950         len = o.length,
8951         i;
8952         for (i = 0; i < len; i += 1) {
8953             a.push(doEncode(o[i]), ',');
8954         }
8955         // Overwrite trailing comma (or empty string)
8956         a[a.length - 1] = ']';
8957         return a.join("");
8958     },
8959     encodeObject = function(o) {
8960         var a = ["{", ""],
8961         // Note empty string in case there are no serializable members.
8962         i;
8963         for (i in o) {
8964             if (!useHasOwn || o.hasOwnProperty(i)) {
8965                 a.push(doEncode(i), ":", doEncode(o[i]), ',');
8966             }
8967         }
8968         // Overwrite trailing comma (or empty string)
8969         a[a.length - 1] = '}';
8970         return a.join("");
8971     };
8972
8973     /**
8974      * <p>Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
8975      * <b>The returned value includes enclosing double quotation marks.</b></p>
8976      * <p>The default return format is "yyyy-mm-ddThh:mm:ss".</p>
8977      * <p>To override this:</p><pre><code>
8978 Ext.JSON.encodeDate = function(d) {
8979     return Ext.Date.format(d, '"Y-m-d"');
8980 };
8981      </code></pre>
8982      * @param {Date} d The Date to encode
8983      * @return {String} The string literal to use in a JSON string.
8984      */
8985     this.encodeDate = function(o) {
8986         return '"' + o.getFullYear() + "-"
8987         + pad(o.getMonth() + 1) + "-"
8988         + pad(o.getDate()) + "T"
8989         + pad(o.getHours()) + ":"
8990         + pad(o.getMinutes()) + ":"
8991         + pad(o.getSeconds()) + '"';
8992     };
8993
8994     /**
8995      * Encodes an Object, Array or other value
8996      * @param {Object} o The variable to encode
8997      * @return {String} The JSON string
8998      */
8999     this.encode = function() {
9000         var ec;
9001         return function(o) {
9002             if (!ec) {
9003                 // setup encoding function on first access
9004                 ec = isNative() ? JSON.stringify : doEncode;
9005             }
9006             return ec(o);
9007         };
9008     }();
9009
9010
9011     /**
9012      * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
9013      * @param {String} json The JSON string
9014      * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
9015      * @return {Object} The resulting object
9016      */
9017     this.decode = function() {
9018         var dc;
9019         return function(json, safe) {
9020             if (!dc) {
9021                 // setup decoding function on first access
9022                 dc = isNative() ? JSON.parse : doDecode;
9023             }
9024             try {
9025                 return dc(json);
9026             } catch (e) {
9027                 if (safe === true) {
9028                     return null;
9029                 }
9030                 Ext.Error.raise({
9031                     sourceClass: "Ext.JSON",
9032                     sourceMethod: "decode",
9033                     msg: "You're trying to decode an invalid JSON String: " + json
9034                 });
9035             }
9036         };
9037     }();
9038
9039 })();
9040 /**
9041  * Shorthand for {@link Ext.JSON#encode}
9042  * @member Ext
9043  * @method encode
9044  * @alias Ext.JSON#encode
9045  */
9046 Ext.encode = Ext.JSON.encode;
9047 /**
9048  * Shorthand for {@link Ext.JSON#decode}
9049  * @member Ext
9050  * @method decode
9051  * @alias Ext.JSON#decode
9052  */
9053 Ext.decode = Ext.JSON.decode;
9054
9055
9056 /**
9057  * @class Ext
9058
9059  The Ext namespace (global object) encapsulates all classes, singletons, and utility methods provided by Sencha's libraries.</p>
9060  Most user interface Components are at a lower level of nesting in the namespace, but many common utility functions are provided
9061  as direct properties of the Ext namespace.
9062
9063  Also many frequently used methods from other classes are provided as shortcuts within the Ext namespace.
9064  For example {@link Ext#getCmp Ext.getCmp} aliases {@link Ext.ComponentManager#get Ext.ComponentManager.get}.
9065
9066  Many applications are initiated with {@link Ext#onReady Ext.onReady} which is called once the DOM is ready.
9067  This ensures all scripts have been loaded, preventing dependency issues. For example
9068
9069      Ext.onReady(function(){
9070          new Ext.Component({
9071              renderTo: document.body,
9072              html: 'DOM ready!'
9073          });
9074      });
9075
9076 For more information about how to use the Ext classes, see
9077
9078 - <a href="http://www.sencha.com/learn/">The Learning Center</a>
9079 - <a href="http://www.sencha.com/learn/Ext_FAQ">The FAQ</a>
9080 - <a href="http://www.sencha.com/forum/">The forums</a>
9081
9082  * @singleton
9083  * @markdown
9084  */
9085 Ext.apply(Ext, {
9086     userAgent: navigator.userAgent.toLowerCase(),
9087     cache: {},
9088     idSeed: 1000,
9089     windowId: 'ext-window',
9090     documentId: 'ext-document',
9091
9092     /**
9093      * True when the document is fully initialized and ready for action
9094      * @type Boolean
9095      */
9096     isReady: false,
9097
9098     /**
9099      * True to automatically uncache orphaned Ext.Elements periodically
9100      * @type Boolean
9101      */
9102     enableGarbageCollector: true,
9103
9104     /**
9105      * True to automatically purge event listeners during garbageCollection.
9106      * @type Boolean
9107      */
9108     enableListenerCollection: true,
9109
9110     /**
9111      * Generates unique ids. If the element already has an id, it is unchanged
9112      * @param {HTMLElement/Ext.Element} el (optional) The element to generate an id for
9113      * @param {String} prefix (optional) Id prefix (defaults "ext-gen")
9114      * @return {String} The generated Id.
9115      */
9116     id: function(el, prefix) {
9117         var me = this,
9118             sandboxPrefix = '';
9119         el = Ext.getDom(el, true) || {};
9120         if (el === document) {
9121             el.id = me.documentId;
9122         }
9123         else if (el === window) {
9124             el.id = me.windowId;
9125         }
9126         if (!el.id) {
9127             if (me.isSandboxed) {
9128                 if (!me.uniqueGlobalNamespace) {
9129                     me.getUniqueGlobalNamespace();
9130                 }
9131                 sandboxPrefix = me.uniqueGlobalNamespace + '-';
9132             }
9133             el.id = sandboxPrefix + (prefix || "ext-gen") + (++Ext.idSeed);
9134         }
9135         return el.id;
9136     },
9137
9138     /**
9139      * Returns the current document body as an {@link Ext.Element}.
9140      * @return Ext.Element The document body
9141      */
9142     getBody: function() {
9143         return Ext.get(document.body || false);
9144     },
9145
9146     /**
9147      * Returns the current document head as an {@link Ext.Element}.
9148      * @return Ext.Element The document head
9149      * @method
9150      */
9151     getHead: function() {
9152         var head;
9153
9154         return function() {
9155             if (head == undefined) {
9156                 head = Ext.get(document.getElementsByTagName("head")[0]);
9157             }
9158
9159             return head;
9160         };
9161     }(),
9162
9163     /**
9164      * Returns the current HTML document object as an {@link Ext.Element}.
9165      * @return Ext.Element The document
9166      */
9167     getDoc: function() {
9168         return Ext.get(document);
9169     },
9170
9171     /**
9172      * This is shorthand reference to {@link Ext.ComponentManager#get}.
9173      * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#id id}
9174      * @param {String} id The component {@link Ext.Component#id id}
9175      * @return Ext.Component The Component, <tt>undefined</tt> if not found, or <tt>null</tt> if a
9176      * Class was found.
9177     */
9178     getCmp: function(id) {
9179         return Ext.ComponentManager.get(id);
9180     },
9181
9182     /**
9183      * Returns the current orientation of the mobile device
9184      * @return {String} Either 'portrait' or 'landscape'
9185      */
9186     getOrientation: function() {
9187         return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape';
9188     },
9189
9190     /**
9191      * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the
9192      * DOM (if applicable) and calling their destroy functions (if available).  This method is primarily
9193      * intended for arguments of type {@link Ext.Element} and {@link Ext.Component}, but any subclass of
9194      * {@link Ext.util.Observable} can be passed in.  Any number of elements and/or components can be
9195      * passed into this function in a single call as separate arguments.
9196      * @param {Ext.Element/Ext.Component/Ext.Element[]/Ext.Component[]...} arg1
9197      * An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy
9198      */
9199     destroy: function() {
9200         var ln = arguments.length,
9201         i, arg;
9202
9203         for (i = 0; i < ln; i++) {
9204             arg = arguments[i];
9205             if (arg) {
9206                 if (Ext.isArray(arg)) {
9207                     this.destroy.apply(this, arg);
9208                 }
9209                 else if (Ext.isFunction(arg.destroy)) {
9210                     arg.destroy();
9211                 }
9212                 else if (arg.dom) {
9213                     arg.remove();
9214                 }
9215             }
9216         }
9217     },
9218
9219     /**
9220      * Execute a callback function in a particular scope. If no function is passed the call is ignored.
9221      *
9222      * For example, these lines are equivalent:
9223      *
9224      *     Ext.callback(myFunc, this, [arg1, arg2]);
9225      *     Ext.isFunction(myFunc) && myFunc.apply(this, [arg1, arg2]);
9226      *
9227      * @param {Function} callback The callback to execute
9228      * @param {Object} scope (optional) The scope to execute in
9229      * @param {Array} args (optional) The arguments to pass to the function
9230      * @param {Number} delay (optional) Pass a number to delay the call by a number of milliseconds.
9231      */
9232     callback: function(callback, scope, args, delay){
9233         if(Ext.isFunction(callback)){
9234             args = args || [];
9235             scope = scope || window;
9236             if (delay) {
9237                 Ext.defer(callback, delay, scope, args);
9238             } else {
9239                 callback.apply(scope, args);
9240             }
9241         }
9242     },
9243
9244     /**
9245      * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
9246      * @param {String} value The string to encode
9247      * @return {String} The encoded text
9248      */
9249     htmlEncode : function(value) {
9250         return Ext.String.htmlEncode(value);
9251     },
9252
9253     /**
9254      * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
9255      * @param {String} value The string to decode
9256      * @return {String} The decoded text
9257      */
9258     htmlDecode : function(value) {
9259          return Ext.String.htmlDecode(value);
9260     },
9261
9262     /**
9263      * Appends content to the query string of a URL, handling logic for whether to place
9264      * a question mark or ampersand.
9265      * @param {String} url The URL to append to.
9266      * @param {String} s The content to append to the URL.
9267      * @return (String) The resulting URL
9268      */
9269     urlAppend : function(url, s) {
9270         if (!Ext.isEmpty(s)) {
9271             return url + (url.indexOf('?') === -1 ? '?' : '&') + s;
9272         }
9273         return url;
9274     }
9275 });
9276
9277
9278 Ext.ns = Ext.namespace;
9279
9280 // for old browsers
9281 window.undefined = window.undefined;
9282
9283 /**
9284  * @class Ext
9285  * Ext core utilities and functions.
9286  * @singleton
9287  */
9288 (function(){
9289 /*
9290 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
9291 FF 4.0.1    - Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
9292 FF 5.0      - Mozilla/5.0 (Windows NT 6.1; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0
9293
9294 IE6         - Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1;)
9295 IE7         - Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1;)
9296 IE8         - Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)
9297 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)
9298
9299 Chrome 11   - Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.60 Safari/534.24
9300
9301 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
9302
9303 Opera 11.11 - Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11
9304 */
9305     var check = function(regex){
9306             return regex.test(Ext.userAgent);
9307         },
9308         isStrict = document.compatMode == "CSS1Compat",
9309         version = function (is, regex) {
9310             var m;
9311             return (is && (m = regex.exec(Ext.userAgent))) ? parseFloat(m[1]) : 0;
9312         },
9313         docMode = document.documentMode,
9314         isOpera = check(/opera/),
9315         isOpera10_5 = isOpera && check(/version\/10\.5/),
9316         isChrome = check(/\bchrome\b/),
9317         isWebKit = check(/webkit/),
9318         isSafari = !isChrome && check(/safari/),
9319         isSafari2 = isSafari && check(/applewebkit\/4/), // unique to Safari 2
9320         isSafari3 = isSafari && check(/version\/3/),
9321         isSafari4 = isSafari && check(/version\/4/),
9322         isSafari5 = isSafari && check(/version\/5/),
9323         isIE = !isOpera && check(/msie/),
9324         isIE7 = isIE && (check(/msie 7/) || docMode == 7),
9325         isIE8 = isIE && (check(/msie 8/) && docMode != 7 && docMode != 9 || docMode == 8),
9326         isIE9 = isIE && (check(/msie 9/) && docMode != 7 && docMode != 8 || docMode == 9),
9327         isIE6 = isIE && check(/msie 6/),
9328         isGecko = !isWebKit && check(/gecko/),
9329         isGecko3 = isGecko && check(/rv:1\.9/),
9330         isGecko4 = isGecko && check(/rv:2\.0/),
9331         isGecko5 = isGecko && check(/rv:5\./),
9332         isFF3_0 = isGecko3 && check(/rv:1\.9\.0/),
9333         isFF3_5 = isGecko3 && check(/rv:1\.9\.1/),
9334         isFF3_6 = isGecko3 && check(/rv:1\.9\.2/),
9335         isWindows = check(/windows|win32/),
9336         isMac = check(/macintosh|mac os x/),
9337         isLinux = check(/linux/),
9338         scrollbarSize = null,
9339         chromeVersion = version(true, /\bchrome\/(\d+\.\d+)/),
9340         firefoxVersion = version(true, /\bfirefox\/(\d+\.\d+)/),
9341         ieVersion = version(isIE, /msie (\d+\.\d+)/),
9342         operaVersion = version(isOpera, /version\/(\d+\.\d+)/),
9343         safariVersion = version(isSafari, /version\/(\d+\.\d+)/),
9344         webKitVersion = version(isWebKit, /webkit\/(\d+\.\d+)/),
9345         isSecure = /^https/i.test(window.location.protocol);
9346
9347     // remove css image flicker
9348     try {
9349         document.execCommand("BackgroundImageCache", false, true);
9350     } catch(e) {}
9351
9352     function dumpObject (object) {
9353         var member, members = [];
9354
9355         // Cannot use Ext.encode since it can recurse endlessly (if we're lucky)
9356         // ...and the data could be prettier!
9357         Ext.Object.each(object, function (name, value) {
9358             if (typeof(value) === "function") {
9359                 return;
9360             }
9361
9362             if (!Ext.isDefined(value) || value === null ||
9363                     Ext.isDate(value) ||
9364                     Ext.isString(value) || (typeof(value) == "number") ||
9365                     Ext.isBoolean(value)) {
9366                 member = Ext.encode(value);
9367             } else if (Ext.isArray(value)) {
9368                 member = '[ ]';
9369             } else if (Ext.isObject(value)) {
9370                 member = '{ }';
9371             } else {
9372                 member = 'undefined';
9373             }
9374             members.push(Ext.encode(name) + ': ' + member);
9375         });
9376
9377         if (members.length) {
9378             return ' \nData: {\n  ' + members.join(',\n  ') + '\n}';
9379         }
9380         return '';
9381     }
9382
9383     function log (message) {
9384         var options, dump,
9385             con = Ext.global.console,
9386             level = 'log',
9387             indent = log.indent || 0,
9388             stack;
9389
9390         log.indent = indent;
9391
9392         if (!Ext.isString(message)) {
9393             options = message;
9394             message = options.msg || '';
9395             level = options.level || level;
9396             dump = options.dump;
9397             stack = options.stack;
9398
9399             if (options.indent) {
9400                 ++log.indent;
9401             } else if (options.outdent) {
9402                 log.indent = indent = Math.max(indent - 1, 0);
9403             }
9404
9405             if (dump && !(con && con.dir)) {
9406                 message += dumpObject(dump);
9407                 dump = null;
9408             }
9409         }
9410
9411         if (arguments.length > 1) {
9412             message += Array.prototype.slice.call(arguments, 1).join('');
9413         }
9414
9415         message = indent ? Ext.String.repeat('   ', indent) + message : message;
9416         // w/o console, all messages are equal, so munge the level into the message:
9417         if (level != 'log') {
9418             message = '[' + level.charAt(0).toUpperCase() + '] ' + message;
9419         }
9420
9421         // Not obvious, but 'console' comes and goes when Firebug is turned on/off, so
9422         // an early test may fail either direction if Firebug is toggled.
9423         //
9424         if (con) { // if (Firebug-like console)
9425             if (con[level]) {
9426                 con[level](message);
9427             } else {
9428                 con.log(message);
9429             }
9430
9431             if (dump) {
9432                 con.dir(dump);
9433             }
9434
9435             if (stack && con.trace) {
9436                 // Firebug's console.error() includes a trace already...
9437                 if (!con.firebug || level != 'error') {
9438                     con.trace();
9439                 }
9440             }
9441         } else {
9442             if (Ext.isOpera) {
9443                 opera.postError(message);
9444             } else {
9445                 var out = log.out,
9446                     max = log.max;
9447
9448                 if (out.length >= max) {
9449                     // this formula allows out.max to change (via debugger), where the
9450                     // more obvious "max/4" would not quite be the same
9451                     Ext.Array.erase(out, 0, out.length - 3 * Math.floor(max / 4)); // keep newest 75%
9452                 }
9453
9454                 out.push(message);
9455             }
9456         }
9457
9458         // Mostly informational, but the Ext.Error notifier uses them:
9459         ++log.count;
9460         ++log.counters[level];
9461     }
9462
9463     log.count = 0;
9464     log.counters = { error: 0, warn: 0, info: 0, log: 0 };
9465     log.out = [];
9466     log.max = 250;
9467     log.show = function () {
9468         window.open('','extlog').document.write([
9469             '<html><head><script type="text/javascript">',
9470                 'var lastCount = 0;',
9471                 'function update () {',
9472                     'var ext = window.opener.Ext,',
9473                         'extlog = ext && ext.log;',
9474                     'if (extlog && extlog.out && lastCount != extlog.count) {',
9475                         'lastCount = extlog.count;',
9476                         'var s = "<tt>" + extlog.out.join("<br>").replace(/[ ]/g, "&nbsp;") + "</tt>";',
9477                         'document.body.innerHTML = s;',
9478                     '}',
9479                     'setTimeout(update, 1000);',
9480                 '}',
9481                 'setTimeout(update, 1000);',
9482             '</script></head><body></body></html>'].join(''));
9483     };
9484
9485     Ext.setVersion('extjs', '4.0.7');
9486     Ext.apply(Ext, {
9487         /**
9488          * URL to a blank file used by Ext when in secure mode for iframe src and onReady src to prevent
9489          * the IE insecure content warning (<tt>'about:blank'</tt>, except for IE in secure mode, which is <tt>'javascript:""'</tt>).
9490          * @type String
9491          */
9492         SSL_SECURE_URL : isSecure && isIE ? 'javascript:""' : 'about:blank',
9493
9494         /**
9495          * True if the {@link Ext.fx.Anim} Class is available
9496          * @type Boolean
9497          * @property enableFx
9498          */
9499
9500         /**
9501          * True to scope the reset CSS to be just applied to Ext components. Note that this wraps root containers
9502          * with an additional element. Also remember that when you turn on this option, you have to use ext-all-scoped {
9503          * unless you use the bootstrap.js to load your javascript, in which case it will be handled for you.
9504          * @type Boolean
9505          */
9506         scopeResetCSS : Ext.buildSettings.scopeResetCSS,
9507
9508         /**
9509          * EXPERIMENTAL - True to cascade listener removal to child elements when an element is removed.
9510          * Currently not optimized for performance.
9511          * @type Boolean
9512          */
9513         enableNestedListenerRemoval : false,
9514
9515         /**
9516          * Indicates whether to use native browser parsing for JSON methods.
9517          * This option is ignored if the browser does not support native JSON methods.
9518          * <b>Note: Native JSON methods will not work with objects that have functions.
9519          * Also, property names must be quoted, otherwise the data will not parse.</b> (Defaults to false)
9520          * @type Boolean
9521          */
9522         USE_NATIVE_JSON : false,
9523
9524         /**
9525          * Return the dom node for the passed String (id), dom node, or Ext.Element.
9526          * Optional 'strict' flag is needed for IE since it can return 'name' and
9527          * 'id' elements by using getElementById.
9528          * Here are some examples:
9529          * <pre><code>
9530 // gets dom node based on id
9531 var elDom = Ext.getDom('elId');
9532 // gets dom node based on the dom node
9533 var elDom1 = Ext.getDom(elDom);
9534
9535 // If we don&#39;t know if we are working with an
9536 // Ext.Element or a dom node use Ext.getDom
9537 function(el){
9538     var dom = Ext.getDom(el);
9539     // do something with the dom node
9540 }
9541          * </code></pre>
9542          * <b>Note</b>: the dom node to be found actually needs to exist (be rendered, etc)
9543          * when this method is called to be successful.
9544          * @param {String/HTMLElement/Ext.Element} el
9545          * @return HTMLElement
9546          */
9547         getDom : function(el, strict) {
9548             if (!el || !document) {
9549                 return null;
9550             }
9551             if (el.dom) {
9552                 return el.dom;
9553             } else {
9554                 if (typeof el == 'string') {
9555                     var e = document.getElementById(el);
9556                     // IE returns elements with the 'name' and 'id' attribute.
9557                     // we do a strict check to return the element with only the id attribute
9558                     if (e && isIE && strict) {
9559                         if (el == e.getAttribute('id')) {
9560                             return e;
9561                         } else {
9562                             return null;
9563                         }
9564                     }
9565                     return e;
9566                 } else {
9567                     return el;
9568                 }
9569             }
9570         },
9571
9572         /**
9573          * Removes a DOM node from the document.
9574          * <p>Removes this element from the document, removes all DOM event listeners, and deletes the cache reference.
9575          * All DOM event listeners are removed from this element. If {@link Ext#enableNestedListenerRemoval Ext.enableNestedListenerRemoval} is
9576          * <code>true</code>, then DOM event listeners are also removed from all child nodes. The body node
9577          * will be ignored if passed in.</p>
9578          * @param {HTMLElement} node The node to remove
9579          * @method
9580          */
9581         removeNode : isIE6 || isIE7 ? function() {
9582             var d;
9583             return function(n){
9584                 if(n && n.tagName != 'BODY'){
9585                     (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
9586                     d = d || document.createElement('div');
9587                     d.appendChild(n);
9588                     d.innerHTML = '';
9589                     delete Ext.cache[n.id];
9590                 }
9591             };
9592         }() : function(n) {
9593             if (n && n.parentNode && n.tagName != 'BODY') {
9594                 (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
9595                 n.parentNode.removeChild(n);
9596                 delete Ext.cache[n.id];
9597             }
9598         },
9599
9600         isStrict: isStrict,
9601
9602         isIEQuirks: isIE && !isStrict,
9603
9604         /**
9605          * True if the detected browser is Opera.
9606          * @type Boolean
9607          */
9608         isOpera : isOpera,
9609
9610         /**
9611          * True if the detected browser is Opera 10.5x.
9612          * @type Boolean
9613          */
9614         isOpera10_5 : isOpera10_5,
9615
9616         /**
9617          * True if the detected browser uses WebKit.
9618          * @type Boolean
9619          */
9620         isWebKit : isWebKit,
9621
9622         /**
9623          * True if the detected browser is Chrome.
9624          * @type Boolean
9625          */
9626         isChrome : isChrome,
9627
9628         /**
9629          * True if the detected browser is Safari.
9630          * @type Boolean
9631          */
9632         isSafari : isSafari,
9633
9634         /**
9635          * True if the detected browser is Safari 3.x.
9636          * @type Boolean
9637          */
9638         isSafari3 : isSafari3,
9639
9640         /**
9641          * True if the detected browser is Safari 4.x.
9642          * @type Boolean
9643          */
9644         isSafari4 : isSafari4,
9645
9646         /**
9647          * True if the detected browser is Safari 5.x.
9648          * @type Boolean
9649          */
9650         isSafari5 : isSafari5,
9651
9652         /**
9653          * True if the detected browser is Safari 2.x.
9654          * @type Boolean
9655          */
9656         isSafari2 : isSafari2,
9657
9658         /**
9659          * True if the detected browser is Internet Explorer.
9660          * @type Boolean
9661          */
9662         isIE : isIE,
9663
9664         /**
9665          * True if the detected browser is Internet Explorer 6.x.
9666          * @type Boolean
9667          */
9668         isIE6 : isIE6,
9669
9670         /**
9671          * True if the detected browser is Internet Explorer 7.x.
9672          * @type Boolean
9673          */
9674         isIE7 : isIE7,
9675
9676         /**
9677          * True if the detected browser is Internet Explorer 8.x.
9678          * @type Boolean
9679          */
9680         isIE8 : isIE8,
9681
9682         /**
9683          * True if the detected browser is Internet Explorer 9.x.
9684          * @type Boolean
9685          */
9686         isIE9 : isIE9,
9687
9688         /**
9689          * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox).
9690          * @type Boolean
9691          */
9692         isGecko : isGecko,
9693
9694         /**
9695          * True if the detected browser uses a Gecko 1.9+ layout engine (e.g. Firefox 3.x).
9696          * @type Boolean
9697          */
9698         isGecko3 : isGecko3,
9699
9700         /**
9701          * True if the detected browser uses a Gecko 2.0+ layout engine (e.g. Firefox 4.x).
9702          * @type Boolean
9703          */
9704         isGecko4 : isGecko4,
9705
9706         /**
9707          * True if the detected browser uses a Gecko 5.0+ layout engine (e.g. Firefox 5.x).
9708          * @type Boolean
9709          */
9710         isGecko5 : isGecko5,
9711
9712         /**
9713          * True if the detected browser uses FireFox 3.0
9714          * @type Boolean
9715          */
9716         isFF3_0 : isFF3_0,
9717
9718         /**
9719          * True if the detected browser uses FireFox 3.5
9720          * @type Boolean
9721          */
9722         isFF3_5 : isFF3_5,
9723
9724         /**
9725          * True if the detected browser uses FireFox 3.6
9726          * @type Boolean
9727          */
9728         isFF3_6 : isFF3_6,
9729
9730         /**
9731          * True if the detected browser uses FireFox 4
9732          * @type Boolean
9733          */
9734         isFF4 : 4 <= firefoxVersion && firefoxVersion < 5,
9735
9736         /**
9737          * True if the detected browser uses FireFox 5
9738          * @type Boolean
9739          */
9740         isFF5 : 5 <= firefoxVersion && firefoxVersion < 6,
9741
9742         /**
9743          * True if the detected platform is Linux.
9744          * @type Boolean
9745          */
9746         isLinux : isLinux,
9747
9748         /**
9749          * True if the detected platform is Windows.
9750          * @type Boolean
9751          */
9752         isWindows : isWindows,
9753
9754         /**
9755          * True if the detected platform is Mac OS.
9756          * @type Boolean
9757          */
9758         isMac : isMac,
9759
9760         /**
9761          * The current version of Chrome (0 if the browser is not Chrome).
9762          * @type Number
9763          */
9764         chromeVersion: chromeVersion,
9765
9766         /**
9767          * The current version of Firefox (0 if the browser is not Firefox).
9768          * @type Number
9769          */
9770         firefoxVersion: firefoxVersion,
9771
9772         /**
9773          * The current version of IE (0 if the browser is not IE). This does not account
9774          * for the documentMode of the current page, which is factored into {@link #isIE7},
9775          * {@link #isIE8} and {@link #isIE9}. Thus this is not always true:
9776          *
9777          *      Ext.isIE8 == (Ext.ieVersion == 8)
9778          *
9779          * @type Number
9780          * @markdown
9781          */
9782         ieVersion: ieVersion,
9783
9784         /**
9785          * The current version of Opera (0 if the browser is not Opera).
9786          * @type Number
9787          */
9788         operaVersion: operaVersion,
9789
9790         /**
9791          * The current version of Safari (0 if the browser is not Safari).
9792          * @type Number
9793          */
9794         safariVersion: safariVersion,
9795
9796         /**
9797          * The current version of WebKit (0 if the browser does not use WebKit).
9798          * @type Number
9799          */
9800         webKitVersion: webKitVersion,
9801
9802         /**
9803          * True if the page is running over SSL
9804          * @type Boolean
9805          */
9806         isSecure: isSecure,
9807
9808         /**
9809          * URL to a 1x1 transparent gif image used by Ext to create inline icons with CSS background images.
9810          * In older versions of IE, this defaults to "http://sencha.com/s.gif" and you should change this to a URL on your server.
9811          * For other browsers it uses an inline data URL.
9812          * @type String
9813          */
9814         BLANK_IMAGE_URL : (isIE6 || isIE7) ? '/' + '/www.sencha.com/s.gif' : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
9815
9816         /**
9817          * <p>Utility method for returning a default value if the passed value is empty.</p>
9818          * <p>The value is deemed to be empty if it is<div class="mdetail-params"><ul>
9819          * <li>null</li>
9820          * <li>undefined</li>
9821          * <li>an empty array</li>
9822          * <li>a zero length string (Unless the <tt>allowBlank</tt> parameter is <tt>true</tt>)</li>
9823          * </ul></div>
9824          * @param {Object} value The value to test
9825          * @param {Object} defaultValue The value to return if the original value is empty
9826          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
9827          * @return {Object} value, if non-empty, else defaultValue
9828          * @deprecated 4.0.0 Use {@link Ext#valueFrom} instead
9829          */
9830         value : function(v, defaultValue, allowBlank){
9831             return Ext.isEmpty(v, allowBlank) ? defaultValue : v;
9832         },
9833
9834         /**
9835          * Escapes the passed string for use in a regular expression
9836          * @param {String} str
9837          * @return {String}
9838          * @deprecated 4.0.0 Use {@link Ext.String#escapeRegex} instead
9839          */
9840         escapeRe : function(s) {
9841             return s.replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1");
9842         },
9843
9844         /**
9845          * Applies event listeners to elements by selectors when the document is ready.
9846          * The event name is specified with an <tt>&#64;</tt> suffix.
9847          * <pre><code>
9848 Ext.addBehaviors({
9849     // add a listener for click on all anchors in element with id foo
9850     '#foo a&#64;click' : function(e, t){
9851         // do something
9852     },
9853
9854     // add the same listener to multiple selectors (separated by comma BEFORE the &#64;)
9855     '#foo a, #bar span.some-class&#64;mouseover' : function(){
9856         // do something
9857     }
9858 });
9859          * </code></pre>
9860          * @param {Object} obj The list of behaviors to apply
9861          */
9862         addBehaviors : function(o){
9863             if(!Ext.isReady){
9864                 Ext.onReady(function(){
9865                     Ext.addBehaviors(o);
9866                 });
9867             } else {
9868                 var cache = {}, // simple cache for applying multiple behaviors to same selector does query multiple times
9869                     parts,
9870                     b,
9871                     s;
9872                 for (b in o) {
9873                     if ((parts = b.split('@'))[1]) { // for Object prototype breakers
9874                         s = parts[0];
9875                         if(!cache[s]){
9876                             cache[s] = Ext.select(s);
9877                         }
9878                         cache[s].on(parts[1], o[b]);
9879                     }
9880                 }
9881                 cache = null;
9882             }
9883         },
9884
9885         /**
9886          * Returns the size of the browser scrollbars. This can differ depending on
9887          * operating system settings, such as the theme or font size.
9888          * @param {Boolean} force (optional) true to force a recalculation of the value.
9889          * @return {Object} An object containing the width of a vertical scrollbar and the
9890          * height of a horizontal scrollbar.
9891          */
9892         getScrollbarSize: function (force) {
9893             if(!Ext.isReady){
9894                 return 0;
9895             }
9896
9897             if(force === true || scrollbarSize === null){
9898                 // BrowserBug: IE9
9899                 // When IE9 positions an element offscreen via offsets, the offsetWidth is
9900                 // inaccurately reported. For IE9 only, we render on screen before removing.
9901                 var cssClass = Ext.isIE9 ? '' : Ext.baseCSSPrefix + 'hide-offsets',
9902                     // Append our div, do our calculation and then remove it
9903                     div = Ext.getBody().createChild('<div class="' + cssClass + '" style="width:100px;height:50px;overflow:hidden;"><div style="height:200px;"></div></div>'),
9904                     child = div.child('div', true),
9905                     w1 = child.offsetWidth;
9906
9907                 div.setStyle('overflow', (Ext.isWebKit || Ext.isGecko) ? 'auto' : 'scroll');
9908
9909                 var w2 = child.offsetWidth, width = w1 - w2;
9910                 div.remove();
9911
9912                 // We assume width == height for now. TODO: is this always true?
9913                 scrollbarSize = { width: width, height: width };
9914             }
9915
9916             return scrollbarSize;
9917         },
9918
9919         /**
9920          * Utility method for getting the width of the browser's vertical scrollbar. This
9921          * can differ depending on operating system settings, such as the theme or font size.
9922          *
9923          * This method is deprected in favor of {@link #getScrollbarSize}.
9924          *
9925          * @param {Boolean} force (optional) true to force a recalculation of the value.
9926          * @return {Number} The width of a vertical scrollbar.
9927          * @deprecated
9928          */
9929         getScrollBarWidth: function(force){
9930             var size = Ext.getScrollbarSize(force);
9931             return size.width + 2; // legacy fudge factor
9932         },
9933
9934         /**
9935          * Copies a set of named properties fom the source object to the destination object.
9936          *
9937          * Example:
9938          *
9939          *     ImageComponent = Ext.extend(Ext.Component, {
9940          *         initComponent: function() {
9941          *             this.autoEl = { tag: 'img' };
9942          *             MyComponent.superclass.initComponent.apply(this, arguments);
9943          *             this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height');
9944          *         }
9945          *     });
9946          *
9947          * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
9948          *
9949          * @param {Object} dest The destination object.
9950          * @param {Object} source The source object.
9951          * @param {String/String[]} names Either an Array of property names, or a comma-delimited list
9952          * of property names to copy.
9953          * @param {Boolean} usePrototypeKeys (Optional) Defaults to false. Pass true to copy keys off of the prototype as well as the instance.
9954          * @return {Object} The modified object.
9955          */
9956         copyTo : function(dest, source, names, usePrototypeKeys){
9957             if(typeof names == 'string'){
9958                 names = names.split(/[,;\s]/);
9959             }
9960             Ext.each(names, function(name){
9961                 if(usePrototypeKeys || source.hasOwnProperty(name)){
9962                     dest[name] = source[name];
9963                 }
9964             }, this);
9965             return dest;
9966         },
9967
9968         /**
9969          * Attempts to destroy and then remove a set of named properties of the passed object.
9970          * @param {Object} o The object (most likely a Component) who's properties you wish to destroy.
9971          * @param {String...} args One or more names of the properties to destroy and remove from the object.
9972          */
9973         destroyMembers : function(o){
9974             for (var i = 1, a = arguments, len = a.length; i < len; i++) {
9975                 Ext.destroy(o[a[i]]);
9976                 delete o[a[i]];
9977             }
9978         },
9979
9980         /**
9981          * Logs a message. If a console is present it will be used. On Opera, the method
9982          * "opera.postError" is called. In other cases, the message is logged to an array
9983          * "Ext.log.out". An attached debugger can watch this array and view the log. The
9984          * log buffer is limited to a maximum of "Ext.log.max" entries (defaults to 250).
9985          * The `Ext.log.out` array can also be written to a popup window by entering the
9986          * following in the URL bar (a "bookmarklet"):
9987          *
9988          *    javascript:void(Ext.log.show());
9989          *
9990          * If additional parameters are passed, they are joined and appended to the message.
9991          * A technique for tracing entry and exit of a function is this:
9992          *
9993          *      function foo () {
9994          *          Ext.log({ indent: 1 }, '>> foo');
9995          *
9996          *          // log statements in here or methods called from here will be indented
9997          *          // by one step
9998          *
9999          *          Ext.log({ outdent: 1 }, '<< foo');
10000          *      }
10001          *
10002          * This method does nothing in a release build.
10003          *
10004          * @param {String/Object} message The message to log or an options object with any
10005          * of the following properties:
10006          *
10007          *  - `msg`: The message to log (required).
10008          *  - `level`: One of: "error", "warn", "info" or "log" (the default is "log").
10009          *  - `dump`: An object to dump to the log as part of the message.
10010          *  - `stack`: True to include a stack trace in the log.
10011          *  - `indent`: Cause subsequent log statements to be indented one step.
10012          *  - `outdent`: Cause this and following statements to be one step less indented.
10013          * @markdown
10014          */
10015         log :
10016             log ||
10017             Ext.emptyFn,
10018
10019         /**
10020          * Partitions the set into two sets: a true set and a false set.
10021          * Example:
10022          * Example2:
10023          * <pre><code>
10024 // Example 1:
10025 Ext.partition([true, false, true, true, false]); // [[true, true, true], [false, false]]
10026
10027 // Example 2:
10028 Ext.partition(
10029     Ext.query("p"),
10030     function(val){
10031         return val.className == "class1"
10032     }
10033 );
10034 // true are those paragraph elements with a className of "class1",
10035 // false set are those that do not have that className.
10036          * </code></pre>
10037          * @param {Array/NodeList} arr The array to partition
10038          * @param {Function} truth (optional) a function to determine truth.  If this is omitted the element
10039          * itself must be able to be evaluated for its truthfulness.
10040          * @return {Array} [array of truish values, array of falsy values]
10041          * @deprecated 4.0.0 Will be removed in the next major version
10042          */
10043         partition : function(arr, truth){
10044             var ret = [[],[]];
10045             Ext.each(arr, function(v, i, a) {
10046                 ret[ (truth && truth(v, i, a)) || (!truth && v) ? 0 : 1].push(v);
10047             });
10048             return ret;
10049         },
10050
10051         /**
10052          * Invokes a method on each item in an Array.
10053          * <pre><code>
10054 // Example:
10055 Ext.invoke(Ext.query("p"), "getAttribute", "id");
10056 // [el1.getAttribute("id"), el2.getAttribute("id"), ..., elN.getAttribute("id")]
10057          * </code></pre>
10058          * @param {Array/NodeList} arr The Array of items to invoke the method on.
10059          * @param {String} methodName The method name to invoke.
10060          * @param {Object...} args Arguments to send into the method invocation.
10061          * @return {Array} The results of invoking the method on each item in the array.
10062          * @deprecated 4.0.0 Will be removed in the next major version
10063          */
10064         invoke : function(arr, methodName){
10065             var ret = [],
10066                 args = Array.prototype.slice.call(arguments, 2);
10067             Ext.each(arr, function(v,i) {
10068                 if (v && typeof v[methodName] == 'function') {
10069                     ret.push(v[methodName].apply(v, args));
10070                 } else {
10071                     ret.push(undefined);
10072                 }
10073             });
10074             return ret;
10075         },
10076
10077         /**
10078          * <p>Zips N sets together.</p>
10079          * <pre><code>
10080 // Example 1:
10081 Ext.zip([1,2,3],[4,5,6]); // [[1,4],[2,5],[3,6]]
10082 // Example 2:
10083 Ext.zip(
10084     [ "+", "-", "+"],
10085     [  12,  10,  22],
10086     [  43,  15,  96],
10087     function(a, b, c){
10088         return "$" + a + "" + b + "." + c
10089     }
10090 ); // ["$+12.43", "$-10.15", "$+22.96"]
10091          * </code></pre>
10092          * @param {Array/NodeList...} arr This argument may be repeated. Array(s) to contribute values.
10093          * @param {Function} zipper (optional) The last item in the argument list. This will drive how the items are zipped together.
10094          * @return {Array} The zipped set.
10095          * @deprecated 4.0.0 Will be removed in the next major version
10096          */
10097         zip : function(){
10098             var parts = Ext.partition(arguments, function( val ){ return typeof val != 'function'; }),
10099                 arrs = parts[0],
10100                 fn = parts[1][0],
10101                 len = Ext.max(Ext.pluck(arrs, "length")),
10102                 ret = [];
10103
10104             for (var i = 0; i < len; i++) {
10105                 ret[i] = [];
10106                 if(fn){
10107                     ret[i] = fn.apply(fn, Ext.pluck(arrs, i));
10108                 }else{
10109                     for (var j = 0, aLen = arrs.length; j < aLen; j++){
10110                         ret[i].push( arrs[j][i] );
10111                     }
10112                 }
10113             }
10114             return ret;
10115         },
10116
10117         /**
10118          * Turns an array into a sentence, joined by a specified connector - e.g.:
10119          * Ext.toSentence(['Adama', 'Tigh', 'Roslin']); //'Adama, Tigh and Roslin'
10120          * Ext.toSentence(['Adama', 'Tigh', 'Roslin'], 'or'); //'Adama, Tigh or Roslin'
10121          * @param {String[]} items The array to create a sentence from
10122          * @param {String} connector The string to use to connect the last two words. Usually 'and' or 'or' - defaults to 'and'.
10123          * @return {String} The sentence string
10124          * @deprecated 4.0.0 Will be removed in the next major version
10125          */
10126         toSentence: function(items, connector) {
10127             var length = items.length;
10128
10129             if (length <= 1) {
10130                 return items[0];
10131             } else {
10132                 var head = items.slice(0, length - 1),
10133                     tail = items[length - 1];
10134
10135                 return Ext.util.Format.format("{0} {1} {2}", head.join(", "), connector || 'and', tail);
10136             }
10137         },
10138
10139         /**
10140          * By default, Ext intelligently decides whether floating elements should be shimmed. If you are using flash,
10141          * you may want to set this to true.
10142          * @type Boolean
10143          */
10144         useShims: isIE6
10145     });
10146 })();
10147
10148 /**
10149  * Loads Ext.app.Application class and starts it up with given configuration after the page is ready.
10150  *
10151  * See Ext.app.Application for details.
10152  *
10153  * @param {Object} config
10154  */
10155 Ext.application = function(config) {
10156     Ext.require('Ext.app.Application');
10157
10158     Ext.onReady(function() {
10159         Ext.create('Ext.app.Application', config);
10160     });
10161 };
10162
10163 /**
10164  * @class Ext.util.Format
10165
10166 This class is a centralized place for formatting functions. It includes
10167 functions to format various different types of data, such as text, dates and numeric values.
10168
10169 __Localization__
10170 This class contains several options for localization. These can be set once the library has loaded,
10171 all calls to the functions from that point will use the locale settings that were specified.
10172 Options include:
10173 - thousandSeparator
10174 - decimalSeparator
10175 - currenyPrecision
10176 - currencySign
10177 - currencyAtEnd
10178 This class also uses the default date format defined here: {@link Ext.Date#defaultFormat}.
10179
10180 __Using with renderers__
10181 There are two helper functions that return a new function that can be used in conjunction with
10182 grid renderers:
10183
10184     columns: [{
10185         dataIndex: 'date',
10186         renderer: Ext.util.Format.dateRenderer('Y-m-d')
10187     }, {
10188         dataIndex: 'time',
10189         renderer: Ext.util.Format.numberRenderer('0.000')
10190     }]
10191
10192 Functions that only take a single argument can also be passed directly:
10193     columns: [{
10194         dataIndex: 'cost',
10195         renderer: Ext.util.Format.usMoney
10196     }, {
10197         dataIndex: 'productCode',
10198         renderer: Ext.util.Format.uppercase
10199     }]
10200
10201 __Using with XTemplates__
10202 XTemplates can also directly use Ext.util.Format functions:
10203
10204     new Ext.XTemplate([
10205         'Date: {startDate:date("Y-m-d")}',
10206         'Cost: {cost:usMoney}'
10207     ]);
10208
10209  * @markdown
10210  * @singleton
10211  */
10212 (function() {
10213     Ext.ns('Ext.util');
10214
10215     Ext.util.Format = {};
10216     var UtilFormat     = Ext.util.Format,
10217         stripTagsRE    = /<\/?[^>]+>/gi,
10218         stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
10219         nl2brRe        = /\r?\n/g,
10220
10221         // A RegExp to remove from a number format string, all characters except digits and '.'
10222         formatCleanRe  = /[^\d\.]/g,
10223
10224         // A RegExp to remove from a number format string, all characters except digits and the local decimal separator.
10225         // Created on first use. The local decimal separator character must be initialized for this to be created.
10226         I18NFormatCleanRe;
10227
10228     Ext.apply(UtilFormat, {
10229         /**
10230          * @property {String} thousandSeparator
10231          * <p>The character that the {@link #number} function uses as a thousand separator.</p>
10232          * <p>This may be overridden in a locale file.</p>
10233          */
10234         thousandSeparator: ',',
10235
10236         /**
10237          * @property {String} decimalSeparator
10238          * <p>The character that the {@link #number} function uses as a decimal point.</p>
10239          * <p>This may be overridden in a locale file.</p>
10240          */
10241         decimalSeparator: '.',
10242
10243         /**
10244          * @property {Number} currencyPrecision
10245          * <p>The number of decimal places that the {@link #currency} function displays.</p>
10246          * <p>This may be overridden in a locale file.</p>
10247          */
10248         currencyPrecision: 2,
10249
10250         /**
10251          * @property {String} currencySign
10252          * <p>The currency sign that the {@link #currency} function displays.</p>
10253          * <p>This may be overridden in a locale file.</p>
10254          */
10255         currencySign: '$',
10256
10257         /**
10258          * @property {Boolean} currencyAtEnd
10259          * <p>This may be set to <code>true</code> to make the {@link #currency} function
10260          * append the currency sign to the formatted value.</p>
10261          * <p>This may be overridden in a locale file.</p>
10262          */
10263         currencyAtEnd: false,
10264
10265         /**
10266          * Checks a reference and converts it to empty string if it is undefined
10267          * @param {Object} value Reference to check
10268          * @return {Object} Empty string if converted, otherwise the original value
10269          */
10270         undef : function(value) {
10271             return value !== undefined ? value : "";
10272         },
10273
10274         /**
10275          * Checks a reference and converts it to the default value if it's empty
10276          * @param {Object} value Reference to check
10277          * @param {String} defaultValue The value to insert of it's undefined (defaults to "")
10278          * @return {String}
10279          */
10280         defaultValue : function(value, defaultValue) {
10281             return value !== undefined && value !== '' ? value : defaultValue;
10282         },
10283
10284         /**
10285          * Returns a substring from within an original string
10286          * @param {String} value The original text
10287          * @param {Number} start The start index of the substring
10288          * @param {Number} length The length of the substring
10289          * @return {String} The substring
10290          */
10291         substr : function(value, start, length) {
10292             return String(value).substr(start, length);
10293         },
10294
10295         /**
10296          * Converts a string to all lower case letters
10297          * @param {String} value The text to convert
10298          * @return {String} The converted text
10299          */
10300         lowercase : function(value) {
10301             return String(value).toLowerCase();
10302         },
10303
10304         /**
10305          * Converts a string to all upper case letters
10306          * @param {String} value The text to convert
10307          * @return {String} The converted text
10308          */
10309         uppercase : function(value) {
10310             return String(value).toUpperCase();
10311         },
10312
10313         /**
10314          * Format a number as US currency
10315          * @param {Number/String} value The numeric value to format
10316          * @return {String} The formatted currency string
10317          */
10318         usMoney : function(v) {
10319             return UtilFormat.currency(v, '$', 2);
10320         },
10321
10322         /**
10323          * Format a number as a currency
10324          * @param {Number/String} value The numeric value to format
10325          * @param {String} sign The currency sign to use (defaults to {@link #currencySign})
10326          * @param {Number} decimals The number of decimals to use for the currency (defaults to {@link #currencyPrecision})
10327          * @param {Boolean} end True if the currency sign should be at the end of the string (defaults to {@link #currencyAtEnd})
10328          * @return {String} The formatted currency string
10329          */
10330         currency: function(v, currencySign, decimals, end) {
10331             var negativeSign = '',
10332                 format = ",0",
10333                 i = 0;
10334             v = v - 0;
10335             if (v < 0) {
10336                 v = -v;
10337                 negativeSign = '-';
10338             }
10339             decimals = decimals || UtilFormat.currencyPrecision;
10340             format += format + (decimals > 0 ? '.' : '');
10341             for (; i < decimals; i++) {
10342                 format += '0';
10343             }
10344             v = UtilFormat.number(v, format);
10345             if ((end || UtilFormat.currencyAtEnd) === true) {
10346                 return Ext.String.format("{0}{1}{2}", negativeSign, v, currencySign || UtilFormat.currencySign);
10347             } else {
10348                 return Ext.String.format("{0}{1}{2}", negativeSign, currencySign || UtilFormat.currencySign, v);
10349             }
10350         },
10351
10352         /**
10353          * Formats the passed date using the specified format pattern.
10354          * @param {String/Date} value The value to format. If a string is passed, it is converted to a Date by the Javascript
10355          * Date object's <a href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method.
10356          * @param {String} format (Optional) Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
10357          * @return {String} The formatted date string.
10358          */
10359         date: function(v, format) {
10360             if (!v) {
10361                 return "";
10362             }
10363             if (!Ext.isDate(v)) {
10364                 v = new Date(Date.parse(v));
10365             }
10366             return Ext.Date.dateFormat(v, format || Ext.Date.defaultFormat);
10367         },
10368
10369         /**
10370          * Returns a date rendering function that can be reused to apply a date format multiple times efficiently
10371          * @param {String} format Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
10372          * @return {Function} The date formatting function
10373          */
10374         dateRenderer : function(format) {
10375             return function(v) {
10376                 return UtilFormat.date(v, format);
10377             };
10378         },
10379
10380         /**
10381          * Strips all HTML tags
10382          * @param {Object} value The text from which to strip tags
10383          * @return {String} The stripped text
10384          */
10385         stripTags : function(v) {
10386             return !v ? v : String(v).replace(stripTagsRE, "");
10387         },
10388
10389         /**
10390          * Strips all script tags
10391          * @param {Object} value The text from which to strip script tags
10392          * @return {String} The stripped text
10393          */
10394         stripScripts : function(v) {
10395             return !v ? v : String(v).replace(stripScriptsRe, "");
10396         },
10397
10398         /**
10399          * Simple format for a file size (xxx bytes, xxx KB, xxx MB)
10400          * @param {Number/String} size The numeric value to format
10401          * @return {String} The formatted file size
10402          */
10403         fileSize : function(size) {
10404             if (size < 1024) {
10405                 return size + " bytes";
10406             } else if (size < 1048576) {
10407                 return (Math.round(((size*10) / 1024))/10) + " KB";
10408             } else {
10409                 return (Math.round(((size*10) / 1048576))/10) + " MB";
10410             }
10411         },
10412
10413         /**
10414          * It does simple math for use in a template, for example:<pre><code>
10415          * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
10416          * </code></pre>
10417          * @return {Function} A function that operates on the passed value.
10418          * @method
10419          */
10420         math : function(){
10421             var fns = {};
10422
10423             return function(v, a){
10424                 if (!fns[a]) {
10425                     fns[a] = Ext.functionFactory('v', 'return v ' + a + ';');
10426                 }
10427                 return fns[a](v);
10428             };
10429         }(),
10430
10431         /**
10432          * Rounds the passed number to the required decimal precision.
10433          * @param {Number/String} value The numeric value to round.
10434          * @param {Number} precision The number of decimal places to which to round the first parameter's value.
10435          * @return {Number} The rounded value.
10436          */
10437         round : function(value, precision) {
10438             var result = Number(value);
10439             if (typeof precision == 'number') {
10440                 precision = Math.pow(10, precision);
10441                 result = Math.round(value * precision) / precision;
10442             }
10443             return result;
10444         },
10445
10446         /**
10447          * <p>Formats the passed number according to the passed format string.</p>
10448          * <p>The number of digits after the decimal separator character specifies the number of
10449          * decimal places in the resulting string. The <u>local-specific</u> decimal character is used in the result.</p>
10450          * <p>The <i>presence</i> of a thousand separator character in the format string specifies that
10451          * the <u>locale-specific</u> thousand separator (if any) is inserted separating thousand groups.</p>
10452          * <p>By default, "," is expected as the thousand separator, and "." is expected as the decimal separator.</p>
10453          * <p><b>New to Ext JS 4</b></p>
10454          * <p>Locale-specific characters are always used in the formatted output when inserting
10455          * thousand and decimal separators.</p>
10456          * <p>The format string must specify separator characters according to US/UK conventions ("," as the
10457          * thousand separator, and "." as the decimal separator)</p>
10458          * <p>To allow specification of format strings according to local conventions for separator characters, add
10459          * the string <code>/i</code> to the end of the format string.</p>
10460          * <div style="margin-left:40px">examples (123456.789):
10461          * <div style="margin-left:10px">
10462          * 0 - (123456) show only digits, no precision<br>
10463          * 0.00 - (123456.78) show only digits, 2 precision<br>
10464          * 0.0000 - (123456.7890) show only digits, 4 precision<br>
10465          * 0,000 - (123,456) show comma and digits, no precision<br>
10466          * 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
10467          * 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
10468          * To allow specification of the formatting string using UK/US grouping characters (,) and decimal (.) for international numbers, add /i to the end.
10469          * For example: 0.000,00/i
10470          * </div></div>
10471          * @param {Number} v The number to format.
10472          * @param {String} format The way you would like to format this text.
10473          * @return {String} The formatted number.
10474          */
10475         number: function(v, formatString) {
10476             if (!formatString) {
10477                 return v;
10478             }
10479             v = Ext.Number.from(v, NaN);
10480             if (isNaN(v)) {
10481                 return '';
10482             }
10483             var comma = UtilFormat.thousandSeparator,
10484                 dec   = UtilFormat.decimalSeparator,
10485                 i18n  = false,
10486                 neg   = v < 0,
10487                 hasComma,
10488                 psplit;
10489
10490             v = Math.abs(v);
10491
10492             // The "/i" suffix allows caller to use a locale-specific formatting string.
10493             // Clean the format string by removing all but numerals and the decimal separator.
10494             // Then split the format string into pre and post decimal segments according to *what* the
10495             // decimal separator is. If they are specifying "/i", they are using the local convention in the format string.
10496             if (formatString.substr(formatString.length - 2) == '/i') {
10497                 if (!I18NFormatCleanRe) {
10498                     I18NFormatCleanRe = new RegExp('[^\\d\\' + UtilFormat.decimalSeparator + ']','g');
10499                 }
10500                 formatString = formatString.substr(0, formatString.length - 2);
10501                 i18n   = true;
10502                 hasComma = formatString.indexOf(comma) != -1;
10503                 psplit = formatString.replace(I18NFormatCleanRe, '').split(dec);
10504             } else {
10505                 hasComma = formatString.indexOf(',') != -1;
10506                 psplit = formatString.replace(formatCleanRe, '').split('.');
10507             }
10508
10509             if (1 < psplit.length) {
10510                 v = v.toFixed(psplit[1].length);
10511             } else if(2 < psplit.length) {
10512                 Ext.Error.raise({
10513                     sourceClass: "Ext.util.Format",
10514                     sourceMethod: "number",
10515                     value: v,
10516                     formatString: formatString,
10517                     msg: "Invalid number format, should have no more than 1 decimal"
10518                 });
10519             } else {
10520                 v = v.toFixed(0);
10521             }
10522
10523             var fnum = v.toString();
10524
10525             psplit = fnum.split('.');
10526
10527             if (hasComma) {
10528                 var cnum = psplit[0],
10529                     parr = [],
10530                     j    = cnum.length,
10531                     m    = Math.floor(j / 3),
10532                     n    = cnum.length % 3 || 3,
10533                     i;
10534
10535                 for (i = 0; i < j; i += n) {
10536                     if (i !== 0) {
10537                         n = 3;
10538                     }
10539
10540                     parr[parr.length] = cnum.substr(i, n);
10541                     m -= 1;
10542                 }
10543                 fnum = parr.join(comma);
10544                 if (psplit[1]) {
10545                     fnum += dec + psplit[1];
10546                 }
10547             } else {
10548                 if (psplit[1]) {
10549                     fnum = psplit[0] + dec + psplit[1];
10550                 }
10551             }
10552
10553             if (neg) {
10554                 /*
10555                  * Edge case. If we have a very small negative number it will get rounded to 0,
10556                  * however the initial check at the top will still report as negative. Replace
10557                  * everything but 1-9 and check if the string is empty to determine a 0 value.
10558                  */
10559                 neg = fnum.replace(/[^1-9]/g, '') !== '';
10560             }
10561
10562             return (neg ? '-' : '') + formatString.replace(/[\d,?\.?]+/, fnum);
10563         },
10564
10565         /**
10566          * Returns a number rendering function that can be reused to apply a number format multiple times efficiently
10567          * @param {String} format Any valid number format string for {@link #number}
10568          * @return {Function} The number formatting function
10569          */
10570         numberRenderer : function(format) {
10571             return function(v) {
10572                 return UtilFormat.number(v, format);
10573             };
10574         },
10575
10576         /**
10577          * Selectively do a plural form of a word based on a numeric value. For example, in a template,
10578          * {commentCount:plural("Comment")}  would result in "1 Comment" if commentCount was 1 or would be "x Comments"
10579          * if the value is 0 or greater than 1.
10580          * @param {Number} value The value to compare against
10581          * @param {String} singular The singular form of the word
10582          * @param {String} plural (optional) The plural form of the word (defaults to the singular with an "s")
10583          */
10584         plural : function(v, s, p) {
10585             return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
10586         },
10587
10588         /**
10589          * Converts newline characters to the HTML tag &lt;br/>
10590          * @param {String} The string value to format.
10591          * @return {String} The string with embedded &lt;br/> tags in place of newlines.
10592          */
10593         nl2br : function(v) {
10594             return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
10595         },
10596
10597         /**
10598          * Alias for {@link Ext.String#capitalize}.
10599          * @method
10600          * @alias Ext.String#capitalize
10601          */
10602         capitalize: Ext.String.capitalize,
10603
10604         /**
10605          * Alias for {@link Ext.String#ellipsis}.
10606          * @method
10607          * @alias Ext.String#ellipsis
10608          */
10609         ellipsis: Ext.String.ellipsis,
10610
10611         /**
10612          * Alias for {@link Ext.String#format}.
10613          * @method
10614          * @alias Ext.String#format
10615          */
10616         format: Ext.String.format,
10617
10618         /**
10619          * Alias for {@link Ext.String#htmlDecode}.
10620          * @method
10621          * @alias Ext.String#htmlDecode
10622          */
10623         htmlDecode: Ext.String.htmlDecode,
10624
10625         /**
10626          * Alias for {@link Ext.String#htmlEncode}.
10627          * @method
10628          * @alias Ext.String#htmlEncode
10629          */
10630         htmlEncode: Ext.String.htmlEncode,
10631
10632         /**
10633          * Alias for {@link Ext.String#leftPad}.
10634          * @method
10635          * @alias Ext.String#leftPad
10636          */
10637         leftPad: Ext.String.leftPad,
10638
10639         /**
10640          * Alias for {@link Ext.String#trim}.
10641          * @method
10642          * @alias Ext.String#trim
10643          */
10644         trim : Ext.String.trim,
10645
10646         /**
10647          * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
10648          * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
10649          * @param {Number/String} v The encoded margins
10650          * @return {Object} An object with margin sizes for top, right, bottom and left
10651          */
10652         parseBox : function(box) {
10653             if (Ext.isNumber(box)) {
10654                 box = box.toString();
10655             }
10656             var parts  = box.split(' '),
10657                 ln = parts.length;
10658
10659             if (ln == 1) {
10660                 parts[1] = parts[2] = parts[3] = parts[0];
10661             }
10662             else if (ln == 2) {
10663                 parts[2] = parts[0];
10664                 parts[3] = parts[1];
10665             }
10666             else if (ln == 3) {
10667                 parts[3] = parts[1];
10668             }
10669
10670             return {
10671                 top   :parseInt(parts[0], 10) || 0,
10672                 right :parseInt(parts[1], 10) || 0,
10673                 bottom:parseInt(parts[2], 10) || 0,
10674                 left  :parseInt(parts[3], 10) || 0
10675             };
10676         },
10677
10678         /**
10679          * Escapes the passed string for use in a regular expression
10680          * @param {String} str
10681          * @return {String}
10682          */
10683         escapeRegex : function(s) {
10684             return s.replace(/([\-.*+?\^${}()|\[\]\/\\])/g, "\\$1");
10685         }
10686     });
10687 })();
10688
10689 /**
10690  * @class Ext.util.TaskRunner
10691  * Provides the ability to execute one or more arbitrary tasks in a multithreaded
10692  * manner.  Generally, you can use the singleton {@link Ext.TaskManager} instead, but
10693  * if needed, you can create separate instances of TaskRunner.  Any number of
10694  * separate tasks can be started at any time and will run independently of each
10695  * other. Example usage:
10696  * <pre><code>
10697 // Start a simple clock task that updates a div once per second
10698 var updateClock = function(){
10699     Ext.fly('clock').update(new Date().format('g:i:s A'));
10700
10701 var task = {
10702     run: updateClock,
10703     interval: 1000 //1 second
10704 }
10705 var runner = new Ext.util.TaskRunner();
10706 runner.start(task);
10707
10708 // equivalent using TaskManager
10709 Ext.TaskManager.start({
10710     run: updateClock,
10711     interval: 1000
10712 });
10713
10714  * </code></pre>
10715  * <p>See the {@link #start} method for details about how to configure a task object.</p>
10716  * Also see {@link Ext.util.DelayedTask}. 
10717  * 
10718  * @constructor
10719  * @param {Number} [interval=10] The minimum precision in milliseconds supported by this TaskRunner instance
10720  */
10721 Ext.ns('Ext.util');
10722
10723 Ext.util.TaskRunner = function(interval) {
10724     interval = interval || 10;
10725     var tasks = [],
10726     removeQueue = [],
10727     id = 0,
10728     running = false,
10729
10730     // private
10731     stopThread = function() {
10732         running = false;
10733         clearInterval(id);
10734         id = 0;
10735     },
10736
10737     // private
10738     startThread = function() {
10739         if (!running) {
10740             running = true;
10741             id = setInterval(runTasks, interval);
10742         }
10743     },
10744
10745     // private
10746     removeTask = function(t) {
10747         removeQueue.push(t);
10748         if (t.onStop) {
10749             t.onStop.apply(t.scope || t);
10750         }
10751     },
10752
10753     // private
10754     runTasks = function() {
10755         var rqLen = removeQueue.length,
10756             now = new Date().getTime(),
10757             i;
10758
10759         if (rqLen > 0) {
10760             for (i = 0; i < rqLen; i++) {
10761                 Ext.Array.remove(tasks, removeQueue[i]);
10762             }
10763             removeQueue = [];
10764             if (tasks.length < 1) {
10765                 stopThread();
10766                 return;
10767             }
10768         }
10769         i = 0;
10770         var t,
10771             itime,
10772             rt,
10773             len = tasks.length;
10774         for (; i < len; ++i) {
10775             t = tasks[i];
10776             itime = now - t.taskRunTime;
10777             if (t.interval <= itime) {
10778                 rt = t.run.apply(t.scope || t, t.args || [++t.taskRunCount]);
10779                 t.taskRunTime = now;
10780                 if (rt === false || t.taskRunCount === t.repeat) {
10781                     removeTask(t);
10782                     return;
10783                 }
10784             }
10785             if (t.duration && t.duration <= (now - t.taskStartTime)) {
10786                 removeTask(t);
10787             }
10788         }
10789     };
10790
10791     /**
10792      * Starts a new task.
10793      * @method start
10794      * @param {Object} task <p>A config object that supports the following properties:<ul>
10795      * <li><code>run</code> : Function<div class="sub-desc"><p>The function to execute each time the task is invoked. The
10796      * function will be called at each interval and passed the <code>args</code> argument if specified, and the
10797      * current invocation count if not.</p>
10798      * <p>If a particular scope (<code>this</code> reference) is required, be sure to specify it using the <code>scope</code> argument.</p>
10799      * <p>Return <code>false</code> from this function to terminate the task.</p></div></li>
10800      * <li><code>interval</code> : Number<div class="sub-desc">The frequency in milliseconds with which the task
10801      * should be invoked.</div></li>
10802      * <li><code>args</code> : Array<div class="sub-desc">(optional) An array of arguments to be passed to the function
10803      * specified by <code>run</code>. If not specified, the current invocation count is passed.</div></li>
10804      * <li><code>scope</code> : Object<div class="sub-desc">(optional) The scope (<tt>this</tt> reference) in which to execute the
10805      * <code>run</code> function. Defaults to the task config object.</div></li>
10806      * <li><code>duration</code> : Number<div class="sub-desc">(optional) The length of time in milliseconds to invoke
10807      * the task before stopping automatically (defaults to indefinite).</div></li>
10808      * <li><code>repeat</code> : Number<div class="sub-desc">(optional) The number of times to invoke the task before
10809      * stopping automatically (defaults to indefinite).</div></li>
10810      * </ul></p>
10811      * <p>Before each invocation, Ext injects the property <code>taskRunCount</code> into the task object so
10812      * that calculations based on the repeat count can be performed.</p>
10813      * @return {Object} The task
10814      */
10815     this.start = function(task) {
10816         tasks.push(task);
10817         task.taskStartTime = new Date().getTime();
10818         task.taskRunTime = 0;
10819         task.taskRunCount = 0;
10820         startThread();
10821         return task;
10822     };
10823
10824     /**
10825      * Stops an existing running task.
10826      * @method stop
10827      * @param {Object} task The task to stop
10828      * @return {Object} The task
10829      */
10830     this.stop = function(task) {
10831         removeTask(task);
10832         return task;
10833     };
10834
10835     /**
10836      * Stops all tasks that are currently running.
10837      * @method stopAll
10838      */
10839     this.stopAll = function() {
10840         stopThread();
10841         for (var i = 0, len = tasks.length; i < len; i++) {
10842             if (tasks[i].onStop) {
10843                 tasks[i].onStop();
10844             }
10845         }
10846         tasks = [];
10847         removeQueue = [];
10848     };
10849 };
10850
10851 /**
10852  * @class Ext.TaskManager
10853  * @extends Ext.util.TaskRunner
10854  * A static {@link Ext.util.TaskRunner} instance that can be used to start and stop arbitrary tasks.  See
10855  * {@link Ext.util.TaskRunner} for supported methods and task config properties.
10856  * <pre><code>
10857 // Start a simple clock task that updates a div once per second
10858 var task = {
10859     run: function(){
10860         Ext.fly('clock').update(new Date().format('g:i:s A'));
10861     },
10862     interval: 1000 //1 second
10863 }
10864 Ext.TaskManager.start(task);
10865 </code></pre>
10866  * <p>See the {@link #start} method for details about how to configure a task object.</p>
10867  * @singleton
10868  */
10869 Ext.TaskManager = Ext.create('Ext.util.TaskRunner');
10870 /**
10871  * @class Ext.is
10872  * 
10873  * Determines information about the current platform the application is running on.
10874  * 
10875  * @singleton
10876  */
10877 Ext.is = {
10878     init : function(navigator) {
10879         var platforms = this.platforms,
10880             ln = platforms.length,
10881             i, platform;
10882
10883         navigator = navigator || window.navigator;
10884
10885         for (i = 0; i < ln; i++) {
10886             platform = platforms[i];
10887             this[platform.identity] = platform.regex.test(navigator[platform.property]);
10888         }
10889
10890         /**
10891          * @property Desktop True if the browser is running on a desktop machine
10892          * @type {Boolean}
10893          */
10894         this.Desktop = this.Mac || this.Windows || (this.Linux && !this.Android);
10895         /**
10896          * @property Tablet True if the browser is running on a tablet (iPad)
10897          */
10898         this.Tablet = this.iPad;
10899         /**
10900          * @property Phone True if the browser is running on a phone.
10901          * @type {Boolean}
10902          */
10903         this.Phone = !this.Desktop && !this.Tablet;
10904         /**
10905          * @property iOS True if the browser is running on iOS
10906          * @type {Boolean}
10907          */
10908         this.iOS = this.iPhone || this.iPad || this.iPod;
10909         
10910         /**
10911          * @property Standalone Detects when application has been saved to homescreen.
10912          * @type {Boolean}
10913          */
10914         this.Standalone = !!window.navigator.standalone;
10915     },
10916     
10917     /**
10918      * @property iPhone True when the browser is running on a iPhone
10919      * @type {Boolean}
10920      */
10921     platforms: [{
10922         property: 'platform',
10923         regex: /iPhone/i,
10924         identity: 'iPhone'
10925     },
10926     
10927     /**
10928      * @property iPod True when the browser is running on a iPod
10929      * @type {Boolean}
10930      */
10931     {
10932         property: 'platform',
10933         regex: /iPod/i,
10934         identity: 'iPod'
10935     },
10936     
10937     /**
10938      * @property iPad True when the browser is running on a iPad
10939      * @type {Boolean}
10940      */
10941     {
10942         property: 'userAgent',
10943         regex: /iPad/i,
10944         identity: 'iPad'
10945     },
10946     
10947     /**
10948      * @property Blackberry True when the browser is running on a Blackberry
10949      * @type {Boolean}
10950      */
10951     {
10952         property: 'userAgent',
10953         regex: /Blackberry/i,
10954         identity: 'Blackberry'
10955     },
10956     
10957     /**
10958      * @property Android True when the browser is running on an Android device
10959      * @type {Boolean}
10960      */
10961     {
10962         property: 'userAgent',
10963         regex: /Android/i,
10964         identity: 'Android'
10965     },
10966     
10967     /**
10968      * @property Mac True when the browser is running on a Mac
10969      * @type {Boolean}
10970      */
10971     {
10972         property: 'platform',
10973         regex: /Mac/i,
10974         identity: 'Mac'
10975     },
10976     
10977     /**
10978      * @property Windows True when the browser is running on Windows
10979      * @type {Boolean}
10980      */
10981     {
10982         property: 'platform',
10983         regex: /Win/i,
10984         identity: 'Windows'
10985     },
10986     
10987     /**
10988      * @property Linux True when the browser is running on Linux
10989      * @type {Boolean}
10990      */
10991     {
10992         property: 'platform',
10993         regex: /Linux/i,
10994         identity: 'Linux'
10995     }]
10996 };
10997
10998 Ext.is.init();
10999
11000 /**
11001  * @class Ext.supports
11002  *
11003  * Determines information about features are supported in the current environment
11004  * 
11005  * @singleton
11006  */
11007 Ext.supports = {
11008     init : function() {
11009         var doc = document,
11010             div = doc.createElement('div'),
11011             tests = this.tests,
11012             ln = tests.length,
11013             i, test;
11014
11015         div.innerHTML = [
11016             '<div style="height:30px;width:50px;">',
11017                 '<div style="height:20px;width:20px;"></div>',
11018             '</div>',
11019             '<div style="width: 200px; height: 200px; position: relative; padding: 5px;">',
11020                 '<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>',
11021             '</div>',
11022             '<div style="float:left; background-color:transparent;"></div>'
11023         ].join('');
11024
11025         doc.body.appendChild(div);
11026
11027         for (i = 0; i < ln; i++) {
11028             test = tests[i];
11029             this[test.identity] = test.fn.call(this, doc, div);
11030         }
11031
11032         doc.body.removeChild(div);
11033     },
11034
11035     /**
11036      * @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style.
11037      * @type {Boolean}
11038      */
11039     CSS3BoxShadow: Ext.isDefined(document.documentElement.style.boxShadow),
11040
11041     /**
11042      * @property ClassList True if document environment supports the HTML5 classList API.
11043      * @type {Boolean}
11044      */
11045     ClassList: !!document.documentElement.classList,
11046
11047     /**
11048      * @property OrientationChange True if the device supports orientation change
11049      * @type {Boolean}
11050      */
11051     OrientationChange: ((typeof window.orientation != 'undefined') && ('onorientationchange' in window)),
11052     
11053     /**
11054      * @property DeviceMotion True if the device supports device motion (acceleration and rotation rate)
11055      * @type {Boolean}
11056      */
11057     DeviceMotion: ('ondevicemotion' in window),
11058     
11059     /**
11060      * @property Touch True if the device supports touch
11061      * @type {Boolean}
11062      */
11063     // is.Desktop is needed due to the bug in Chrome 5.0.375, Safari 3.1.2
11064     // and Safari 4.0 (they all have 'ontouchstart' in the window object).
11065     Touch: ('ontouchstart' in window) && (!Ext.is.Desktop),
11066
11067     tests: [
11068         /**
11069          * @property Transitions True if the device supports CSS3 Transitions
11070          * @type {Boolean}
11071          */
11072         {
11073             identity: 'Transitions',
11074             fn: function(doc, div) {
11075                 var prefix = [
11076                         'webkit',
11077                         'Moz',
11078                         'o',
11079                         'ms',
11080                         'khtml'
11081                     ],
11082                     TE = 'TransitionEnd',
11083                     transitionEndName = [
11084                         prefix[0] + TE,
11085                         'transitionend', //Moz bucks the prefixing convention
11086                         prefix[2] + TE,
11087                         prefix[3] + TE,
11088                         prefix[4] + TE
11089                     ],
11090                     ln = prefix.length,
11091                     i = 0,
11092                     out = false;
11093                 div = Ext.get(div);
11094                 for (; i < ln; i++) {
11095                     if (div.getStyle(prefix[i] + "TransitionProperty")) {
11096                         Ext.supports.CSS3Prefix = prefix[i];
11097                         Ext.supports.CSS3TransitionEnd = transitionEndName[i];
11098                         out = true;
11099                         break;
11100                     }
11101                 }
11102                 return out;
11103             }
11104         },
11105         
11106         /**
11107          * @property RightMargin True if the device supports right margin.
11108          * See https://bugs.webkit.org/show_bug.cgi?id=13343 for why this is needed.
11109          * @type {Boolean}
11110          */
11111         {
11112             identity: 'RightMargin',
11113             fn: function(doc, div) {
11114                 var view = doc.defaultView;
11115                 return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px');
11116             }
11117         },
11118
11119         /**
11120          * @property DisplayChangeInputSelectionBug True if INPUT elements lose their
11121          * selection when their display style is changed. Essentially, if a text input
11122          * has focus and its display style is changed, the I-beam disappears.
11123          * 
11124          * This bug is encountered due to the work around in place for the {@link #RightMargin}
11125          * bug. This has been observed in Safari 4.0.4 and older, and appears to be fixed
11126          * in Safari 5. It's not clear if Safari 4.1 has the bug, but it has the same WebKit
11127          * version number as Safari 5 (according to http://unixpapa.com/js/gecko.html).
11128          */
11129         {
11130             identity: 'DisplayChangeInputSelectionBug',
11131             fn: function() {
11132                 var webKitVersion = Ext.webKitVersion;
11133                 // WebKit but older than Safari 5 or Chrome 6:
11134                 return 0 < webKitVersion && webKitVersion < 533;
11135             }
11136         },
11137
11138         /**
11139          * @property DisplayChangeTextAreaSelectionBug True if TEXTAREA elements lose their
11140          * selection when their display style is changed. Essentially, if a text area has
11141          * focus and its display style is changed, the I-beam disappears.
11142          *
11143          * This bug is encountered due to the work around in place for the {@link #RightMargin}
11144          * bug. This has been observed in Chrome 10 and Safari 5 and older, and appears to
11145          * be fixed in Chrome 11.
11146          */
11147         {
11148             identity: 'DisplayChangeTextAreaSelectionBug',
11149             fn: function() {
11150                 var webKitVersion = Ext.webKitVersion;
11151
11152                 /*
11153                 Has bug w/textarea:
11154
11155                 (Chrome) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US)
11156                             AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127
11157                             Safari/534.16
11158                 (Safari) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us)
11159                             AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5
11160                             Safari/533.21.1
11161
11162                 No bug:
11163
11164                 (Chrome) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7)
11165                             AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.57
11166                             Safari/534.24
11167                 */
11168                 return 0 < webKitVersion && webKitVersion < 534.24;
11169             }
11170         },
11171
11172         /**
11173          * @property TransparentColor True if the device supports transparent color
11174          * @type {Boolean}
11175          */
11176         {
11177             identity: 'TransparentColor',
11178             fn: function(doc, div, view) {
11179                 view = doc.defaultView;
11180                 return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent');
11181             }
11182         },
11183
11184         /**
11185          * @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle()
11186          * @type {Boolean}
11187          */
11188         {
11189             identity: 'ComputedStyle',
11190             fn: function(doc, div, view) {
11191                 view = doc.defaultView;
11192                 return view && view.getComputedStyle;
11193             }
11194         },
11195         
11196         /**
11197          * @property SVG True if the device supports SVG
11198          * @type {Boolean}
11199          */
11200         {
11201             identity: 'Svg',
11202             fn: function(doc) {
11203                 return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect;
11204             }
11205         },
11206     
11207         /**
11208          * @property Canvas True if the device supports Canvas
11209          * @type {Boolean}
11210          */
11211         {
11212             identity: 'Canvas',
11213             fn: function(doc) {
11214                 return !!doc.createElement('canvas').getContext;
11215             }
11216         },
11217         
11218         /**
11219          * @property VML True if the device supports VML
11220          * @type {Boolean}
11221          */
11222         {
11223             identity: 'Vml',
11224             fn: function(doc) {
11225                 var d = doc.createElement("div");
11226                 d.innerHTML = "<!--[if vml]><br><br><![endif]-->";
11227                 return (d.childNodes.length == 2);
11228             }
11229         },
11230         
11231         /**
11232          * @property Float True if the device supports CSS float
11233          * @type {Boolean}
11234          */
11235         {
11236             identity: 'Float',
11237             fn: function(doc, div) {
11238                 return !!div.lastChild.style.cssFloat;
11239             }
11240         },
11241         
11242         /**
11243          * @property AudioTag True if the device supports the HTML5 audio tag
11244          * @type {Boolean}
11245          */
11246         {
11247             identity: 'AudioTag',
11248             fn: function(doc) {
11249                 return !!doc.createElement('audio').canPlayType;
11250             }
11251         },
11252         
11253         /**
11254          * @property History True if the device supports HTML5 history
11255          * @type {Boolean}
11256          */
11257         {
11258             identity: 'History',
11259             fn: function() {
11260                 return !!(window.history && history.pushState);
11261             }
11262         },
11263         
11264         /**
11265          * @property CSS3DTransform True if the device supports CSS3DTransform
11266          * @type {Boolean}
11267          */
11268         {
11269             identity: 'CSS3DTransform',
11270             fn: function() {
11271                 return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41'));
11272             }
11273         },
11274
11275                 /**
11276          * @property CSS3LinearGradient True if the device supports CSS3 linear gradients
11277          * @type {Boolean}
11278          */
11279         {
11280             identity: 'CSS3LinearGradient',
11281             fn: function(doc, div) {
11282                 var property = 'background-image:',
11283                     webkit   = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))',
11284                     w3c      = 'linear-gradient(left top, black, white)',
11285                     moz      = '-moz-' + w3c,
11286                     options  = [property + webkit, property + w3c, property + moz];
11287                 
11288                 div.style.cssText = options.join(';');
11289                 
11290                 return ("" + div.style.backgroundImage).indexOf('gradient') !== -1;
11291             }
11292         },
11293         
11294         /**
11295          * @property CSS3BorderRadius True if the device supports CSS3 border radius
11296          * @type {Boolean}
11297          */
11298         {
11299             identity: 'CSS3BorderRadius',
11300             fn: function(doc, div) {
11301                 var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'],
11302                     pass = false,
11303                     i;
11304                 for (i = 0; i < domPrefixes.length; i++) {
11305                     if (document.body.style[domPrefixes[i]] !== undefined) {
11306                         return true;
11307                     }
11308                 }
11309                 return pass;
11310             }
11311         },
11312         
11313         /**
11314          * @property GeoLocation True if the device supports GeoLocation
11315          * @type {Boolean}
11316          */
11317         {
11318             identity: 'GeoLocation',
11319             fn: function() {
11320                 return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (typeof google != 'undefined' && typeof google.gears != 'undefined');
11321             }
11322         },
11323         /**
11324          * @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events
11325          * @type {Boolean}
11326          */
11327         {
11328             identity: 'MouseEnterLeave',
11329             fn: function(doc, div){
11330                 return ('onmouseenter' in div && 'onmouseleave' in div);
11331             }
11332         },
11333         /**
11334          * @property MouseWheel True if the browser supports the mousewheel event
11335          * @type {Boolean}
11336          */
11337         {
11338             identity: 'MouseWheel',
11339             fn: function(doc, div) {
11340                 return ('onmousewheel' in div);
11341             }
11342         },
11343         /**
11344          * @property Opacity True if the browser supports normal css opacity
11345          * @type {Boolean}
11346          */
11347         {
11348             identity: 'Opacity',
11349             fn: function(doc, div){
11350                 // Not a strict equal comparison in case opacity can be converted to a number.
11351                 if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
11352                     return false;
11353                 }
11354                 div.firstChild.style.cssText = 'opacity:0.73';
11355                 return div.firstChild.style.opacity == '0.73';
11356             }
11357         },
11358         /**
11359          * @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs
11360          * @type {Boolean}
11361          */
11362         {
11363             identity: 'Placeholder',
11364             fn: function(doc) {
11365                 return 'placeholder' in doc.createElement('input');
11366             }
11367         },
11368         
11369         /**
11370          * @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight, 
11371          * getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel.
11372          * @type {Boolean}
11373          */
11374         {
11375             identity: 'Direct2DBug',
11376             fn: function() {
11377                 return Ext.isString(document.body.style.msTransformOrigin);
11378             }
11379         },
11380         /**
11381          * @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements
11382          * @type {Boolean}
11383          */
11384         {
11385             identity: 'BoundingClientRect',
11386             fn: function(doc, div) {
11387                 return Ext.isFunction(div.getBoundingClientRect);
11388             }
11389         },
11390         {
11391             identity: 'IncludePaddingInWidthCalculation',
11392             fn: function(doc, div){
11393                 var el = Ext.get(div.childNodes[1].firstChild);
11394                 return el.getWidth() == 210;
11395             }
11396         },
11397         {
11398             identity: 'IncludePaddingInHeightCalculation',
11399             fn: function(doc, div){
11400                 var el = Ext.get(div.childNodes[1].firstChild);
11401                 return el.getHeight() == 210;
11402             }
11403         },
11404         
11405         /**
11406          * @property ArraySort True if the Array sort native method isn't bugged.
11407          * @type {Boolean}
11408          */
11409         {
11410             identity: 'ArraySort',
11411             fn: function() {
11412                 var a = [1,2,3,4,5].sort(function(){ return 0; });
11413                 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
11414             }
11415         },
11416         /**
11417          * @property Range True if browser support document.createRange native method.
11418          * @type {Boolean}
11419          */
11420         {
11421             identity: 'Range',
11422             fn: function() {
11423                 return !!document.createRange;
11424             }
11425         },
11426         /**
11427          * @property CreateContextualFragment True if browser support CreateContextualFragment range native methods.
11428          * @type {Boolean}
11429          */
11430         {
11431             identity: 'CreateContextualFragment',
11432             fn: function() {
11433                 var range = Ext.supports.Range ? document.createRange() : false;
11434                 
11435                 return range && !!range.createContextualFragment;
11436             }
11437         },
11438
11439         /**
11440          * @property WindowOnError True if browser supports window.onerror.
11441          * @type {Boolean}
11442          */
11443         {
11444             identity: 'WindowOnError',
11445             fn: function () {
11446                 // sadly, we cannot feature detect this...
11447                 return Ext.isIE || Ext.isGecko || Ext.webKitVersion >= 534.16; // Chrome 10+
11448             }
11449         }
11450     ]
11451 };
11452
11453
11454
11455 /*
11456
11457 This file is part of Ext JS 4
11458
11459 Copyright (c) 2011 Sencha Inc
11460
11461 Contact:  http://www.sencha.com/contact
11462
11463 GNU General Public License Usage
11464 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.
11465
11466 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
11467
11468 */
11469 /**
11470  * @class Ext.DomHelper
11471  * @alternateClassName Ext.core.DomHelper
11472  *
11473  * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
11474  * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
11475  * from your DOM building code.</p>
11476  *
11477  * <p><b><u>DomHelper element specification object</u></b></p>
11478  * <p>A specification object is used when creating elements. Attributes of this object
11479  * are assumed to be element attributes, except for 4 special attributes:
11480  * <div class="mdetail-params"><ul>
11481  * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
11482  * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
11483  * same kind of element definition objects to be created and appended. These can be nested
11484  * as deep as you want.</div></li>
11485  * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
11486  * This will end up being either the "class" attribute on a HTML fragment or className
11487  * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
11488  * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
11489  * </ul></div></p>
11490  * <p><b>NOTE:</b> For other arbitrary attributes, the value will currently <b>not</b> be automatically
11491  * HTML-escaped prior to building the element's HTML string. This means that if your attribute value
11492  * contains special characters that would not normally be allowed in a double-quoted attribute value,
11493  * you <b>must</b> manually HTML-encode it beforehand (see {@link Ext.String#htmlEncode}) or risk
11494  * malformed HTML being created. This behavior may change in a future release.</p>
11495  *
11496  * <p><b><u>Insertion methods</u></b></p>
11497  * <p>Commonly used insertion methods:
11498  * <div class="mdetail-params"><ul>
11499  * <li><tt>{@link #append}</tt> : <div class="sub-desc"></div></li>
11500  * <li><tt>{@link #insertBefore}</tt> : <div class="sub-desc"></div></li>
11501  * <li><tt>{@link #insertAfter}</tt> : <div class="sub-desc"></div></li>
11502  * <li><tt>{@link #overwrite}</tt> : <div class="sub-desc"></div></li>
11503  * <li><tt>{@link #createTemplate}</tt> : <div class="sub-desc"></div></li>
11504  * <li><tt>{@link #insertHtml}</tt> : <div class="sub-desc"></div></li>
11505  * </ul></div></p>
11506  *
11507  * <p><b><u>Example</u></b></p>
11508  * <p>This is an example, where an unordered list with 3 children items is appended to an existing
11509  * element with id <tt>'my-div'</tt>:<br>
11510  <pre><code>
11511 var dh = Ext.DomHelper; // create shorthand alias
11512 // specification object
11513 var spec = {
11514     id: 'my-ul',
11515     tag: 'ul',
11516     cls: 'my-list',
11517     // append children after creating
11518     children: [     // may also specify 'cn' instead of 'children'
11519         {tag: 'li', id: 'item0', html: 'List Item 0'},
11520         {tag: 'li', id: 'item1', html: 'List Item 1'},
11521         {tag: 'li', id: 'item2', html: 'List Item 2'}
11522     ]
11523 };
11524 var list = dh.append(
11525     'my-div', // the context element 'my-div' can either be the id or the actual node
11526     spec      // the specification object
11527 );
11528  </code></pre></p>
11529  * <p>Element creation specification parameters in this class may also be passed as an Array of
11530  * specification objects. This can be used to insert multiple sibling nodes into an existing
11531  * container very efficiently. For example, to add more list items to the example above:<pre><code>
11532 dh.append('my-ul', [
11533     {tag: 'li', id: 'item3', html: 'List Item 3'},
11534     {tag: 'li', id: 'item4', html: 'List Item 4'}
11535 ]);
11536  * </code></pre></p>
11537  *
11538  * <p><b><u>Templating</u></b></p>
11539  * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
11540  * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
11541  * insert new elements. Revisiting the example above, we could utilize templating this time:
11542  * <pre><code>
11543 // create the node
11544 var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
11545 // get template
11546 var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
11547
11548 for(var i = 0; i < 5, i++){
11549     tpl.append(list, [i]); // use template to append to the actual node
11550 }
11551  * </code></pre></p>
11552  * <p>An example using a template:<pre><code>
11553 var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
11554
11555 var tpl = new Ext.DomHelper.createTemplate(html);
11556 tpl.append('blog-roll', ['link1', 'http://www.edspencer.net/', "Ed&#39;s Site"]);
11557 tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin&#39;s Site"]);
11558  * </code></pre></p>
11559  *
11560  * <p>The same example using named parameters:<pre><code>
11561 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
11562
11563 var tpl = new Ext.DomHelper.createTemplate(html);
11564 tpl.append('blog-roll', {
11565     id: 'link1',
11566     url: 'http://www.edspencer.net/',
11567     text: "Ed&#39;s Site"
11568 });
11569 tpl.append('blog-roll', {
11570     id: 'link2',
11571     url: 'http://www.dustindiaz.com/',
11572     text: "Dustin&#39;s Site"
11573 });
11574  * </code></pre></p>
11575  *
11576  * <p><b><u>Compiling Templates</u></b></p>
11577  * <p>Templates are applied using regular expressions. The performance is great, but if
11578  * you are adding a bunch of DOM elements using the same template, you can increase
11579  * performance even further by {@link Ext.Template#compile "compiling"} the template.
11580  * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
11581  * broken up at the different variable points and a dynamic function is created and eval'ed.
11582  * The generated function performs string concatenation of these parts and the passed
11583  * variables instead of using regular expressions.
11584  * <pre><code>
11585 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
11586
11587 var tpl = new Ext.DomHelper.createTemplate(html);
11588 tpl.compile();
11589
11590 //... use template like normal
11591  * </code></pre></p>
11592  *
11593  * <p><b><u>Performance Boost</u></b></p>
11594  * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
11595  * of DOM can significantly boost performance.</p>
11596  * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
11597  * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
11598  * results in the creation of a text node. Usage:</p>
11599  * <pre><code>
11600 Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
11601  * </code></pre>
11602  * @singleton
11603  */
11604 Ext.ns('Ext.core');
11605 Ext.core.DomHelper = Ext.DomHelper = function(){
11606     var tempTableEl = null,
11607         emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
11608         tableRe = /^table|tbody|tr|td$/i,
11609         confRe = /tag|children|cn|html$/i,
11610         tableElRe = /td|tr|tbody/i,
11611         endRe = /end/i,
11612         pub,
11613         // kill repeat to save bytes
11614         afterbegin = 'afterbegin',
11615         afterend = 'afterend',
11616         beforebegin = 'beforebegin',
11617         beforeend = 'beforeend',
11618         ts = '<table>',
11619         te = '</table>',
11620         tbs = ts+'<tbody>',
11621         tbe = '</tbody>'+te,
11622         trs = tbs + '<tr>',
11623         tre = '</tr>'+tbe;
11624
11625     // private
11626     function doInsert(el, o, returnElement, pos, sibling, append){
11627         el = Ext.getDom(el);
11628         var newNode;
11629         if (pub.useDom) {
11630             newNode = createDom(o, null);
11631             if (append) {
11632                 el.appendChild(newNode);
11633             } else {
11634                 (sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
11635             }
11636         } else {
11637             newNode = Ext.DomHelper.insertHtml(pos, el, Ext.DomHelper.createHtml(o));
11638         }
11639         return returnElement ? Ext.get(newNode, true) : newNode;
11640     }
11641
11642     function createDom(o, parentNode){
11643         var el,
11644             doc = document,
11645             useSet,
11646             attr,
11647             val,
11648             cn;
11649
11650         if (Ext.isArray(o)) {                       // Allow Arrays of siblings to be inserted
11651             el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
11652             for (var i = 0, l = o.length; i < l; i++) {
11653                 createDom(o[i], el);
11654             }
11655         } else if (typeof o == 'string') {         // Allow a string as a child spec.
11656             el = doc.createTextNode(o);
11657         } else {
11658             el = doc.createElement( o.tag || 'div' );
11659             useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
11660             for (attr in o) {
11661                 if(!confRe.test(attr)){
11662                     val = o[attr];
11663                     if(attr == 'cls'){
11664                         el.className = val;
11665                     }else{
11666                         if(useSet){
11667                             el.setAttribute(attr, val);
11668                         }else{
11669                             el[attr] = val;
11670                         }
11671                     }
11672                 }
11673             }
11674             Ext.DomHelper.applyStyles(el, o.style);
11675
11676             if ((cn = o.children || o.cn)) {
11677                 createDom(cn, el);
11678             } else if (o.html) {
11679                 el.innerHTML = o.html;
11680             }
11681         }
11682         if(parentNode){
11683            parentNode.appendChild(el);
11684         }
11685         return el;
11686     }
11687
11688     // build as innerHTML where available
11689     function createHtml(o){
11690         var b = '',
11691             attr,
11692             val,
11693             key,
11694             cn,
11695             i;
11696
11697         if(typeof o == "string"){
11698             b = o;
11699         } else if (Ext.isArray(o)) {
11700             for (i=0; i < o.length; i++) {
11701                 if(o[i]) {
11702                     b += createHtml(o[i]);
11703                 }
11704             }
11705         } else {
11706             b += '<' + (o.tag = o.tag || 'div');
11707             for (attr in o) {
11708                 val = o[attr];
11709                 if(!confRe.test(attr)){
11710                     if (typeof val == "object") {
11711                         b += ' ' + attr + '="';
11712                         for (key in val) {
11713                             b += key + ':' + val[key] + ';';
11714                         }
11715                         b += '"';
11716                     }else{
11717                         b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
11718                     }
11719                 }
11720             }
11721             // Now either just close the tag or try to add children and close the tag.
11722             if (emptyTags.test(o.tag)) {
11723                 b += '/>';
11724             } else {
11725                 b += '>';
11726                 if ((cn = o.children || o.cn)) {
11727                     b += createHtml(cn);
11728                 } else if(o.html){
11729                     b += o.html;
11730                 }
11731                 b += '</' + o.tag + '>';
11732             }
11733         }
11734         return b;
11735     }
11736
11737     function ieTable(depth, s, h, e){
11738         tempTableEl.innerHTML = [s, h, e].join('');
11739         var i = -1,
11740             el = tempTableEl,
11741             ns;
11742         while(++i < depth){
11743             el = el.firstChild;
11744         }
11745 //      If the result is multiple siblings, then encapsulate them into one fragment.
11746         ns = el.nextSibling;
11747         if (ns){
11748             var df = document.createDocumentFragment();
11749             while(el){
11750                 ns = el.nextSibling;
11751                 df.appendChild(el);
11752                 el = ns;
11753             }
11754             el = df;
11755         }
11756         return el;
11757     }
11758
11759     /**
11760      * @ignore
11761      * Nasty code for IE's broken table implementation
11762      */
11763     function insertIntoTable(tag, where, el, html) {
11764         var node,
11765             before;
11766
11767         tempTableEl = tempTableEl || document.createElement('div');
11768
11769         if(tag == 'td' && (where == afterbegin || where == beforeend) ||
11770            !tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
11771             return null;
11772         }
11773         before = where == beforebegin ? el :
11774                  where == afterend ? el.nextSibling :
11775                  where == afterbegin ? el.firstChild : null;
11776
11777         if (where == beforebegin || where == afterend) {
11778             el = el.parentNode;
11779         }
11780
11781         if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
11782             node = ieTable(4, trs, html, tre);
11783         } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
11784                    (tag == 'tr' && (where == beforebegin || where == afterend))) {
11785             node = ieTable(3, tbs, html, tbe);
11786         } else {
11787             node = ieTable(2, ts, html, te);
11788         }
11789         el.insertBefore(node, before);
11790         return node;
11791     }
11792
11793     /**
11794      * @ignore
11795      * Fix for IE9 createContextualFragment missing method
11796      */
11797     function createContextualFragment(html){
11798         var div = document.createElement("div"),
11799             fragment = document.createDocumentFragment(),
11800             i = 0,
11801             length, childNodes;
11802
11803         div.innerHTML = html;
11804         childNodes = div.childNodes;
11805         length = childNodes.length;
11806
11807         for (; i < length; i++) {
11808             fragment.appendChild(childNodes[i].cloneNode(true));
11809         }
11810
11811         return fragment;
11812     }
11813
11814     pub = {
11815         /**
11816          * Returns the markup for the passed Element(s) config.
11817          * @param {Object} o The DOM object spec (and children)
11818          * @return {String}
11819          */
11820         markup : function(o){
11821             return createHtml(o);
11822         },
11823
11824         /**
11825          * Applies a style specification to an element.
11826          * @param {String/HTMLElement} el The element to apply styles to
11827          * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
11828          * a function which returns such a specification.
11829          */
11830         applyStyles : function(el, styles){
11831             if (styles) {
11832                 el = Ext.fly(el);
11833                 if (typeof styles == "function") {
11834                     styles = styles.call();
11835                 }
11836                 if (typeof styles == "string") {
11837                     styles = Ext.Element.parseStyles(styles);
11838                 }
11839                 if (typeof styles == "object") {
11840                     el.setStyle(styles);
11841                 }
11842             }
11843         },
11844
11845         /**
11846          * Inserts an HTML fragment into the DOM.
11847          * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
11848          *
11849          * For example take the following HTML: `<div>Contents</div>`
11850          *
11851          * Using different `where` values inserts element to the following places:
11852          *
11853          * - beforeBegin: `<HERE><div>Contents</div>`
11854          * - afterBegin: `<div><HERE>Contents</div>`
11855          * - beforeEnd: `<div>Contents<HERE></div>`
11856          * - afterEnd: `<div>Contents</div><HERE>`
11857          *
11858          * @param {HTMLElement/TextNode} el The context element
11859          * @param {String} html The HTML fragment
11860          * @return {HTMLElement} The new node
11861          */
11862         insertHtml : function(where, el, html){
11863             var hash = {},
11864                 hashVal,
11865                 range,
11866                 rangeEl,
11867                 setStart,
11868                 frag,
11869                 rs;
11870
11871             where = where.toLowerCase();
11872             // add these here because they are used in both branches of the condition.
11873             hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
11874             hash[afterend] = ['AfterEnd', 'nextSibling'];
11875
11876             // if IE and context element is an HTMLElement
11877             if (el.insertAdjacentHTML) {
11878                 if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
11879                     return rs;
11880                 }
11881
11882                 // add these two to the hash.
11883                 hash[afterbegin] = ['AfterBegin', 'firstChild'];
11884                 hash[beforeend] = ['BeforeEnd', 'lastChild'];
11885                 if ((hashVal = hash[where])) {
11886                     el.insertAdjacentHTML(hashVal[0], html);
11887                     return el[hashVal[1]];
11888                 }
11889             // if (not IE and context element is an HTMLElement) or TextNode
11890             } else {
11891                 // we cannot insert anything inside a textnode so...
11892                 if (Ext.isTextNode(el)) {
11893                     where = where === 'afterbegin' ? 'beforebegin' : where;
11894                     where = where === 'beforeend' ? 'afterend' : where;
11895                 }
11896                 range = Ext.supports.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
11897                 setStart = 'setStart' + (endRe.test(where) ? 'After' : 'Before');
11898                 if (hash[where]) {
11899                     if (range) {
11900                         range[setStart](el);
11901                         frag = range.createContextualFragment(html);
11902                     } else {
11903                         frag = createContextualFragment(html);
11904                     }
11905                     el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
11906                     return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
11907                 } else {
11908                     rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
11909                     if (el.firstChild) {
11910                         if (range) {
11911                             range[setStart](el[rangeEl]);
11912                             frag = range.createContextualFragment(html);
11913                         } else {
11914                             frag = createContextualFragment(html);
11915                         }
11916
11917                         if(where == afterbegin){
11918                             el.insertBefore(frag, el.firstChild);
11919                         }else{
11920                             el.appendChild(frag);
11921                         }
11922                     } else {
11923                         el.innerHTML = html;
11924                     }
11925                     return el[rangeEl];
11926                 }
11927             }
11928             Ext.Error.raise({
11929                 sourceClass: 'Ext.DomHelper',
11930                 sourceMethod: 'insertHtml',
11931                 htmlToInsert: html,
11932                 targetElement: el,
11933                 msg: 'Illegal insertion point reached: "' + where + '"'
11934             });
11935         },
11936
11937         /**
11938          * Creates new DOM element(s) and inserts them before el.
11939          * @param {String/HTMLElement/Ext.Element} el The context element
11940          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11941          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11942          * @return {HTMLElement/Ext.Element} The new node
11943          */
11944         insertBefore : function(el, o, returnElement){
11945             return doInsert(el, o, returnElement, beforebegin);
11946         },
11947
11948         /**
11949          * Creates new DOM element(s) and inserts them after el.
11950          * @param {String/HTMLElement/Ext.Element} el The context element
11951          * @param {Object} o The DOM object spec (and children)
11952          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11953          * @return {HTMLElement/Ext.Element} The new node
11954          */
11955         insertAfter : function(el, o, returnElement){
11956             return doInsert(el, o, returnElement, afterend, 'nextSibling');
11957         },
11958
11959         /**
11960          * Creates new DOM element(s) and inserts them as the first child of el.
11961          * @param {String/HTMLElement/Ext.Element} el The context element
11962          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11963          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11964          * @return {HTMLElement/Ext.Element} The new node
11965          */
11966         insertFirst : function(el, o, returnElement){
11967             return doInsert(el, o, returnElement, afterbegin, 'firstChild');
11968         },
11969
11970         /**
11971          * Creates new DOM element(s) and appends them to el.
11972          * @param {String/HTMLElement/Ext.Element} el The context element
11973          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11974          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11975          * @return {HTMLElement/Ext.Element} The new node
11976          */
11977         append : function(el, o, returnElement){
11978             return doInsert(el, o, returnElement, beforeend, '', true);
11979         },
11980
11981         /**
11982          * Creates new DOM element(s) and overwrites the contents of el with them.
11983          * @param {String/HTMLElement/Ext.Element} el The context element
11984          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11985          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11986          * @return {HTMLElement/Ext.Element} The new node
11987          */
11988         overwrite : function(el, o, returnElement){
11989             el = Ext.getDom(el);
11990             el.innerHTML = createHtml(o);
11991             return returnElement ? Ext.get(el.firstChild) : el.firstChild;
11992         },
11993
11994         createHtml : createHtml,
11995
11996         /**
11997          * Creates new DOM element(s) without inserting them to the document.
11998          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11999          * @return {HTMLElement} The new uninserted node
12000          * @method
12001          */
12002         createDom: createDom,
12003
12004         /** True to force the use of DOM instead of html fragments @type Boolean */
12005         useDom : false,
12006
12007         /**
12008          * Creates a new Ext.Template from the DOM object spec.
12009          * @param {Object} o The DOM object spec (and children)
12010          * @return {Ext.Template} The new template
12011          */
12012         createTemplate : function(o){
12013             var html = Ext.DomHelper.createHtml(o);
12014             return Ext.create('Ext.Template', html);
12015         }
12016     };
12017     return pub;
12018 }();
12019
12020 /*
12021  * This is code is also distributed under MIT license for use
12022  * with jQuery and prototype JavaScript libraries.
12023  */
12024 /**
12025  * @class Ext.DomQuery
12026 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).
12027 <p>
12028 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>
12029
12030 <p>
12031 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.
12032 </p>
12033 <h4>Element Selectors:</h4>
12034 <ul class="list">
12035     <li> <b>*</b> any element</li>
12036     <li> <b>E</b> an element with the tag E</li>
12037     <li> <b>E F</b> All descendent elements of E that have the tag F</li>
12038     <li> <b>E > F</b> or <b>E/F</b> all direct children elements of E that have the tag F</li>
12039     <li> <b>E + F</b> all elements with the tag F that are immediately preceded by an element with the tag E</li>
12040     <li> <b>E ~ F</b> all elements with the tag F that are preceded by a sibling element with the tag E</li>
12041 </ul>
12042 <h4>Attribute Selectors:</h4>
12043 <p>The use of &#64; and quotes are optional. For example, div[&#64;foo='bar'] is also a valid attribute selector.</p>
12044 <ul class="list">
12045     <li> <b>E[foo]</b> has an attribute "foo"</li>
12046     <li> <b>E[foo=bar]</b> has an attribute "foo" that equals "bar"</li>
12047     <li> <b>E[foo^=bar]</b> has an attribute "foo" that starts with "bar"</li>
12048     <li> <b>E[foo$=bar]</b> has an attribute "foo" that ends with "bar"</li>
12049     <li> <b>E[foo*=bar]</b> has an attribute "foo" that contains the substring "bar"</li>
12050     <li> <b>E[foo%=2]</b> has an attribute "foo" that is evenly divisible by 2</li>
12051     <li> <b>E[foo!=bar]</b> attribute "foo" does not equal "bar"</li>
12052 </ul>
12053 <h4>Pseudo Classes:</h4>
12054 <ul class="list">
12055     <li> <b>E:first-child</b> E is the first child of its parent</li>
12056     <li> <b>E:last-child</b> E is the last child of its parent</li>
12057     <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>
12058     <li> <b>E:nth-child(odd)</b> E is an odd child of its parent</li>
12059     <li> <b>E:nth-child(even)</b> E is an even child of its parent</li>
12060     <li> <b>E:only-child</b> E is the only child of its parent</li>
12061     <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>
12062     <li> <b>E:first</b> the first E in the resultset</li>
12063     <li> <b>E:last</b> the last E in the resultset</li>
12064     <li> <b>E:nth(<i>n</i>)</b> the <i>n</i>th E in the resultset (1 based)</li>
12065     <li> <b>E:odd</b> shortcut for :nth-child(odd)</li>
12066     <li> <b>E:even</b> shortcut for :nth-child(even)</li>
12067     <li> <b>E:contains(foo)</b> E's innerHTML contains the substring "foo"</li>
12068     <li> <b>E:nodeValue(foo)</b> E contains a textNode with a nodeValue that equals "foo"</li>
12069     <li> <b>E:not(S)</b> an E element that does not match simple selector S</li>
12070     <li> <b>E:has(S)</b> an E element that has a descendent that matches simple selector S</li>
12071     <li> <b>E:next(S)</b> an E element whose next sibling matches simple selector S</li>
12072     <li> <b>E:prev(S)</b> an E element whose previous sibling matches simple selector S</li>
12073     <li> <b>E:any(S1|S2|S2)</b> an E element which matches any of the simple selectors S1, S2 or S3//\\</li>
12074 </ul>
12075 <h4>CSS Value Selectors:</h4>
12076 <ul class="list">
12077     <li> <b>E{display=none}</b> css value "display" that equals "none"</li>
12078     <li> <b>E{display^=none}</b> css value "display" that starts with "none"</li>
12079     <li> <b>E{display$=none}</b> css value "display" that ends with "none"</li>
12080     <li> <b>E{display*=none}</b> css value "display" that contains the substring "none"</li>
12081     <li> <b>E{display%=2}</b> css value "display" that is evenly divisible by 2</li>
12082     <li> <b>E{display!=none}</b> css value "display" that does not equal "none"</li>
12083 </ul>
12084  * @singleton
12085  */
12086 Ext.ns('Ext.core');
12087
12088 Ext.core.DomQuery = Ext.DomQuery = function(){
12089     var cache = {},
12090         simpleCache = {},
12091         valueCache = {},
12092         nonSpace = /\S/,
12093         trimRe = /^\s+|\s+$/g,
12094         tplRe = /\{(\d+)\}/g,
12095         modeRe = /^(\s?[\/>+~]\s?|\s|$)/,
12096         tagTokenRe = /^(#)?([\w-\*]+)/,
12097         nthRe = /(\d*)n\+?(\d*)/,
12098         nthRe2 = /\D/,
12099         startIdRe = /^\s*\#/,
12100         // This is for IE MSXML which does not support expandos.
12101     // IE runs the same speed using setAttribute, however FF slows way down
12102     // and Safari completely fails so they need to continue to use expandos.
12103     isIE = window.ActiveXObject ? true : false,
12104     key = 30803;
12105
12106     // this eval is stop the compressor from
12107     // renaming the variable to something shorter
12108     eval("var batch = 30803;");
12109
12110     // Retrieve the child node from a particular
12111     // parent at the specified index.
12112     function child(parent, index){
12113         var i = 0,
12114             n = parent.firstChild;
12115         while(n){
12116             if(n.nodeType == 1){
12117                if(++i == index){
12118                    return n;
12119                }
12120             }
12121             n = n.nextSibling;
12122         }
12123         return null;
12124     }
12125
12126     // retrieve the next element node
12127     function next(n){
12128         while((n = n.nextSibling) && n.nodeType != 1);
12129         return n;
12130     }
12131
12132     // retrieve the previous element node
12133     function prev(n){
12134         while((n = n.previousSibling) && n.nodeType != 1);
12135         return n;
12136     }
12137
12138     // Mark each child node with a nodeIndex skipping and
12139     // removing empty text nodes.
12140     function children(parent){
12141         var n = parent.firstChild,
12142         nodeIndex = -1,
12143         nextNode;
12144         while(n){
12145             nextNode = n.nextSibling;
12146             // clean worthless empty nodes.
12147             if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){
12148             parent.removeChild(n);
12149             }else{
12150             // add an expando nodeIndex
12151             n.nodeIndex = ++nodeIndex;
12152             }
12153             n = nextNode;
12154         }
12155         return this;
12156     }
12157
12158
12159     // nodeSet - array of nodes
12160     // cls - CSS Class
12161     function byClassName(nodeSet, cls){
12162         if(!cls){
12163             return nodeSet;
12164         }
12165         var result = [], ri = -1;
12166         for(var i = 0, ci; ci = nodeSet[i]; i++){
12167             if((' '+ci.className+' ').indexOf(cls) != -1){
12168                 result[++ri] = ci;
12169             }
12170         }
12171         return result;
12172     };
12173
12174     function attrValue(n, attr){
12175         // if its an array, use the first node.
12176         if(!n.tagName && typeof n.length != "undefined"){
12177             n = n[0];
12178         }
12179         if(!n){
12180             return null;
12181         }
12182
12183         if(attr == "for"){
12184             return n.htmlFor;
12185         }
12186         if(attr == "class" || attr == "className"){
12187             return n.className;
12188         }
12189         return n.getAttribute(attr) || n[attr];
12190
12191     };
12192
12193
12194     // ns - nodes
12195     // mode - false, /, >, +, ~
12196     // tagName - defaults to "*"
12197     function getNodes(ns, mode, tagName){
12198         var result = [], ri = -1, cs;
12199         if(!ns){
12200             return result;
12201         }
12202         tagName = tagName || "*";
12203         // convert to array
12204         if(typeof ns.getElementsByTagName != "undefined"){
12205             ns = [ns];
12206         }
12207
12208         // no mode specified, grab all elements by tagName
12209         // at any depth
12210         if(!mode){
12211             for(var i = 0, ni; ni = ns[i]; i++){
12212                 cs = ni.getElementsByTagName(tagName);
12213                 for(var j = 0, ci; ci = cs[j]; j++){
12214                     result[++ri] = ci;
12215                 }
12216             }
12217         // Direct Child mode (/ or >)
12218         // E > F or E/F all direct children elements of E that have the tag
12219         } else if(mode == "/" || mode == ">"){
12220             var utag = tagName.toUpperCase();
12221             for(var i = 0, ni, cn; ni = ns[i]; i++){
12222                 cn = ni.childNodes;
12223                 for(var j = 0, cj; cj = cn[j]; j++){
12224                     if(cj.nodeName == utag || cj.nodeName == tagName  || tagName == '*'){
12225                         result[++ri] = cj;
12226                     }
12227                 }
12228             }
12229         // Immediately Preceding mode (+)
12230         // E + F all elements with the tag F that are immediately preceded by an element with the tag E
12231         }else if(mode == "+"){
12232             var utag = tagName.toUpperCase();
12233             for(var i = 0, n; n = ns[i]; i++){
12234                 while((n = n.nextSibling) && n.nodeType != 1);
12235                 if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){
12236                     result[++ri] = n;
12237                 }
12238             }
12239         // Sibling mode (~)
12240         // E ~ F all elements with the tag F that are preceded by a sibling element with the tag E
12241         }else if(mode == "~"){
12242             var utag = tagName.toUpperCase();
12243             for(var i = 0, n; n = ns[i]; i++){
12244                 while((n = n.nextSibling)){
12245                     if (n.nodeName == utag || n.nodeName == tagName || tagName == '*'){
12246                         result[++ri] = n;
12247                     }
12248                 }
12249             }
12250         }
12251         return result;
12252     }
12253
12254     function concat(a, b){
12255         if(b.slice){
12256             return a.concat(b);
12257         }
12258         for(var i = 0, l = b.length; i < l; i++){
12259             a[a.length] = b[i];
12260         }
12261         return a;
12262     }
12263
12264     function byTag(cs, tagName){
12265         if(cs.tagName || cs == document){
12266             cs = [cs];
12267         }
12268         if(!tagName){
12269             return cs;
12270         }
12271         var result = [], ri = -1;
12272         tagName = tagName.toLowerCase();
12273         for(var i = 0, ci; ci = cs[i]; i++){
12274             if(ci.nodeType == 1 && ci.tagName.toLowerCase() == tagName){
12275                 result[++ri] = ci;
12276             }
12277         }
12278         return result;
12279     }
12280
12281     function byId(cs, id){
12282         if(cs.tagName || cs == document){
12283             cs = [cs];
12284         }
12285         if(!id){
12286             return cs;
12287         }
12288         var result = [], ri = -1;
12289         for(var i = 0, ci; ci = cs[i]; i++){
12290             if(ci && ci.id == id){
12291                 result[++ri] = ci;
12292                 return result;
12293             }
12294         }
12295         return result;
12296     }
12297
12298     // operators are =, !=, ^=, $=, *=, %=, |= and ~=
12299     // custom can be "{"
12300     function byAttribute(cs, attr, value, op, custom){
12301         var result = [],
12302             ri = -1,
12303             useGetStyle = custom == "{",
12304             fn = Ext.DomQuery.operators[op],
12305             a,
12306             xml,
12307             hasXml;
12308
12309         for(var i = 0, ci; ci = cs[i]; i++){
12310             // skip non-element nodes.
12311             if(ci.nodeType != 1){
12312                 continue;
12313             }
12314             // only need to do this for the first node
12315             if(!hasXml){
12316                 xml = Ext.DomQuery.isXml(ci);
12317                 hasXml = true;
12318             }
12319
12320             // we only need to change the property names if we're dealing with html nodes, not XML
12321             if(!xml){
12322                 if(useGetStyle){
12323                     a = Ext.DomQuery.getStyle(ci, attr);
12324                 } else if (attr == "class" || attr == "className"){
12325                     a = ci.className;
12326                 } else if (attr == "for"){
12327                     a = ci.htmlFor;
12328                 } else if (attr == "href"){
12329                     // getAttribute href bug
12330                     // http://www.glennjones.net/Post/809/getAttributehrefbug.htm
12331                     a = ci.getAttribute("href", 2);
12332                 } else{
12333                     a = ci.getAttribute(attr);
12334                 }
12335             }else{
12336                 a = ci.getAttribute(attr);
12337             }
12338             if((fn && fn(a, value)) || (!fn && a)){
12339                 result[++ri] = ci;
12340             }
12341         }
12342         return result;
12343     }
12344
12345     function byPseudo(cs, name, value){
12346         return Ext.DomQuery.pseudos[name](cs, value);
12347     }
12348
12349     function nodupIEXml(cs){
12350         var d = ++key,
12351             r;
12352         cs[0].setAttribute("_nodup", d);
12353         r = [cs[0]];
12354         for(var i = 1, len = cs.length; i < len; i++){
12355             var c = cs[i];
12356             if(!c.getAttribute("_nodup") != d){
12357                 c.setAttribute("_nodup", d);
12358                 r[r.length] = c;
12359             }
12360         }
12361         for(var i = 0, len = cs.length; i < len; i++){
12362             cs[i].removeAttribute("_nodup");
12363         }
12364         return r;
12365     }
12366
12367     function nodup(cs){
12368         if(!cs){
12369             return [];
12370         }
12371         var len = cs.length, c, i, r = cs, cj, ri = -1;
12372         if(!len || typeof cs.nodeType != "undefined" || len == 1){
12373             return cs;
12374         }
12375         if(isIE && typeof cs[0].selectSingleNode != "undefined"){
12376             return nodupIEXml(cs);
12377         }
12378         var d = ++key;
12379         cs[0]._nodup = d;
12380         for(i = 1; c = cs[i]; i++){
12381             if(c._nodup != d){
12382                 c._nodup = d;
12383             }else{
12384                 r = [];
12385                 for(var j = 0; j < i; j++){
12386                     r[++ri] = cs[j];
12387                 }
12388                 for(j = i+1; cj = cs[j]; j++){
12389                     if(cj._nodup != d){
12390                         cj._nodup = d;
12391                         r[++ri] = cj;
12392                     }
12393                 }
12394                 return r;
12395             }
12396         }
12397         return r;
12398     }
12399
12400     function quickDiffIEXml(c1, c2){
12401         var d = ++key,
12402             r = [];
12403         for(var i = 0, len = c1.length; i < len; i++){
12404             c1[i].setAttribute("_qdiff", d);
12405         }
12406         for(var i = 0, len = c2.length; i < len; i++){
12407             if(c2[i].getAttribute("_qdiff") != d){
12408                 r[r.length] = c2[i];
12409             }
12410         }
12411         for(var i = 0, len = c1.length; i < len; i++){
12412            c1[i].removeAttribute("_qdiff");
12413         }
12414         return r;
12415     }
12416
12417     function quickDiff(c1, c2){
12418         var len1 = c1.length,
12419             d = ++key,
12420             r = [];
12421         if(!len1){
12422             return c2;
12423         }
12424         if(isIE && typeof c1[0].selectSingleNode != "undefined"){
12425             return quickDiffIEXml(c1, c2);
12426         }
12427         for(var i = 0; i < len1; i++){
12428             c1[i]._qdiff = d;
12429         }
12430         for(var i = 0, len = c2.length; i < len; i++){
12431             if(c2[i]._qdiff != d){
12432                 r[r.length] = c2[i];
12433             }
12434         }
12435         return r;
12436     }
12437
12438     function quickId(ns, mode, root, id){
12439         if(ns == root){
12440            var d = root.ownerDocument || root;
12441            return d.getElementById(id);
12442         }
12443         ns = getNodes(ns, mode, "*");
12444         return byId(ns, id);
12445     }
12446
12447     return {
12448         getStyle : function(el, name){
12449             return Ext.fly(el).getStyle(name);
12450         },
12451         /**
12452          * Compiles a selector/xpath query into a reusable function. The returned function
12453          * takes one parameter "root" (optional), which is the context node from where the query should start.
12454          * @param {String} selector The selector/xpath query
12455          * @param {String} type (optional) Either "select" (the default) or "simple" for a simple selector match
12456          * @return {Function}
12457          */
12458         compile : function(path, type){
12459             type = type || "select";
12460
12461             // setup fn preamble
12462             var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
12463                 mode,
12464                 lastPath,
12465                 matchers = Ext.DomQuery.matchers,
12466                 matchersLn = matchers.length,
12467                 modeMatch,
12468                 // accept leading mode switch
12469                 lmode = path.match(modeRe);
12470
12471             if(lmode && lmode[1]){
12472                 fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
12473                 path = path.replace(lmode[1], "");
12474             }
12475
12476             // strip leading slashes
12477             while(path.substr(0, 1)=="/"){
12478                 path = path.substr(1);
12479             }
12480
12481             while(path && lastPath != path){
12482                 lastPath = path;
12483                 var tokenMatch = path.match(tagTokenRe);
12484                 if(type == "select"){
12485                     if(tokenMatch){
12486                         // ID Selector
12487                         if(tokenMatch[1] == "#"){
12488                             fn[fn.length] = 'n = quickId(n, mode, root, "'+tokenMatch[2]+'");';
12489                         }else{
12490                             fn[fn.length] = 'n = getNodes(n, mode, "'+tokenMatch[2]+'");';
12491                         }
12492                         path = path.replace(tokenMatch[0], "");
12493                     }else if(path.substr(0, 1) != '@'){
12494                         fn[fn.length] = 'n = getNodes(n, mode, "*");';
12495                     }
12496                 // type of "simple"
12497                 }else{
12498                     if(tokenMatch){
12499                         if(tokenMatch[1] == "#"){
12500                             fn[fn.length] = 'n = byId(n, "'+tokenMatch[2]+'");';
12501                         }else{
12502                             fn[fn.length] = 'n = byTag(n, "'+tokenMatch[2]+'");';
12503                         }
12504                         path = path.replace(tokenMatch[0], "");
12505                     }
12506                 }
12507                 while(!(modeMatch = path.match(modeRe))){
12508                     var matched = false;
12509                     for(var j = 0; j < matchersLn; j++){
12510                         var t = matchers[j];
12511                         var m = path.match(t.re);
12512                         if(m){
12513                             fn[fn.length] = t.select.replace(tplRe, function(x, i){
12514                                 return m[i];
12515                             });
12516                             path = path.replace(m[0], "");
12517                             matched = true;
12518                             break;
12519                         }
12520                     }
12521                     // prevent infinite loop on bad selector
12522                     if(!matched){
12523                         Ext.Error.raise({
12524                             sourceClass: 'Ext.DomQuery',
12525                             sourceMethod: 'compile',
12526                             msg: 'Error parsing selector. Parsing failed at "' + path + '"'
12527                         });
12528                     }
12529                 }
12530                 if(modeMatch[1]){
12531                     fn[fn.length] = 'mode="'+modeMatch[1].replace(trimRe, "")+'";';
12532                     path = path.replace(modeMatch[1], "");
12533                 }
12534             }
12535             // close fn out
12536             fn[fn.length] = "return nodup(n);\n}";
12537
12538             // eval fn and return it
12539             eval(fn.join(""));
12540             return f;
12541         },
12542
12543         /**
12544          * Selects an array of DOM nodes using JavaScript-only implementation.
12545          *
12546          * Use {@link #select} to take advantage of browsers built-in support for CSS selectors.
12547          *
12548          * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
12549          * @param {HTMLElement/String} root (optional) The start of the query (defaults to document).
12550          * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
12551          * no matches, and empty Array is returned.
12552          */
12553         jsSelect: function(path, root, type){
12554             // set root to doc if not specified.
12555             root = root || document;
12556
12557             if(typeof root == "string"){
12558                 root = document.getElementById(root);
12559             }
12560             var paths = path.split(","),
12561                 results = [];
12562
12563             // loop over each selector
12564             for(var i = 0, len = paths.length; i < len; i++){
12565                 var subPath = paths[i].replace(trimRe, "");
12566                 // compile and place in cache
12567                 if(!cache[subPath]){
12568                     cache[subPath] = Ext.DomQuery.compile(subPath);
12569                     if(!cache[subPath]){
12570                         Ext.Error.raise({
12571                             sourceClass: 'Ext.DomQuery',
12572                             sourceMethod: 'jsSelect',
12573                             msg: subPath + ' is not a valid selector'
12574                         });
12575                     }
12576                 }
12577                 var result = cache[subPath](root);
12578                 if(result && result != document){
12579                     results = results.concat(result);
12580                 }
12581             }
12582
12583             // if there were multiple selectors, make sure dups
12584             // are eliminated
12585             if(paths.length > 1){
12586                 return nodup(results);
12587             }
12588             return results;
12589         },
12590
12591         isXml: function(el) {
12592             var docEl = (el ? el.ownerDocument || el : 0).documentElement;
12593             return docEl ? docEl.nodeName !== "HTML" : false;
12594         },
12595
12596         /**
12597          * Selects an array of DOM nodes by CSS/XPath selector.
12598          *
12599          * Uses [document.querySelectorAll][0] if browser supports that, otherwise falls back to
12600          * {@link Ext.DomQuery#jsSelect} to do the work.
12601          *
12602          * Aliased as {@link Ext#query}.
12603          *
12604          * [0]: https://developer.mozilla.org/en/DOM/document.querySelectorAll
12605          *
12606          * @param {String} path The selector/xpath query
12607          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12608          * @return {HTMLElement[]} An array of DOM elements (not a NodeList as returned by `querySelectorAll`).
12609          * Empty array when no matches.
12610          * @method
12611          */
12612         select : document.querySelectorAll ? function(path, root, type) {
12613             root = root || document;
12614             /* 
12615              * Safari 3.x can't handle uppercase or unicode characters when in quirks mode.
12616              */
12617             if (!Ext.DomQuery.isXml(root) && !(Ext.isSafari3 && !Ext.isStrict)) { 
12618                 try {
12619                     /*
12620                      * This checking here is to "fix" the behaviour of querySelectorAll
12621                      * for non root document queries. The way qsa works is intentional,
12622                      * however it's definitely not the expected way it should work.
12623                      * More info: http://ejohn.org/blog/thoughts-on-queryselectorall/
12624                      *
12625                      * We only modify the path for single selectors (ie, no multiples),
12626                      * without a full parser it makes it difficult to do this correctly.
12627                      */
12628                     var isDocumentRoot = root.nodeType === 9,
12629                         _path = path,
12630                         _root = root;
12631
12632                     if (!isDocumentRoot && path.indexOf(',') === -1 && !startIdRe.test(path)) {
12633                         _path = '#' + Ext.id(root) + ' ' + path;
12634                         _root = root.parentNode;
12635                     }
12636                     return Ext.Array.toArray(_root.querySelectorAll(_path));
12637                 }
12638                 catch (e) {
12639                 }
12640             }
12641             return Ext.DomQuery.jsSelect.call(this, path, root, type);
12642         } : function(path, root, type) {
12643             return Ext.DomQuery.jsSelect.call(this, path, root, type);
12644         },
12645
12646         /**
12647          * Selects a single element.
12648          * @param {String} selector The selector/xpath query
12649          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12650          * @return {HTMLElement} The DOM element which matched the selector.
12651          */
12652         selectNode : function(path, root){
12653             return Ext.DomQuery.select(path, root)[0];
12654         },
12655
12656         /**
12657          * Selects the value of a node, optionally replacing null with the defaultValue.
12658          * @param {String} selector The selector/xpath query
12659          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12660          * @param {String} defaultValue (optional) When specified, this is return as empty value.
12661          * @return {String}
12662          */
12663         selectValue : function(path, root, defaultValue){
12664             path = path.replace(trimRe, "");
12665             if(!valueCache[path]){
12666                 valueCache[path] = Ext.DomQuery.compile(path, "select");
12667             }
12668             var n = valueCache[path](root), v;
12669             n = n[0] ? n[0] : n;
12670
12671             // overcome a limitation of maximum textnode size
12672             // Rumored to potentially crash IE6 but has not been confirmed.
12673             // http://reference.sitepoint.com/javascript/Node/normalize
12674             // https://developer.mozilla.org/En/DOM/Node.normalize
12675             if (typeof n.normalize == 'function') n.normalize();
12676
12677             v = (n && n.firstChild ? n.firstChild.nodeValue : null);
12678             return ((v === null||v === undefined||v==='') ? defaultValue : v);
12679         },
12680
12681         /**
12682          * Selects the value of a node, parsing integers and floats. Returns the defaultValue, or 0 if none is specified.
12683          * @param {String} selector The selector/xpath query
12684          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12685          * @param {Number} defaultValue (optional) When specified, this is return as empty value.
12686          * @return {Number}
12687          */
12688         selectNumber : function(path, root, defaultValue){
12689             var v = Ext.DomQuery.selectValue(path, root, defaultValue || 0);
12690             return parseFloat(v);
12691         },
12692
12693         /**
12694          * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
12695          * @param {String/HTMLElement/HTMLElement[]} el An element id, element or array of elements
12696          * @param {String} selector The simple selector to test
12697          * @return {Boolean}
12698          */
12699         is : function(el, ss){
12700             if(typeof el == "string"){
12701                 el = document.getElementById(el);
12702             }
12703             var isArray = Ext.isArray(el),
12704                 result = Ext.DomQuery.filter(isArray ? el : [el], ss);
12705             return isArray ? (result.length == el.length) : (result.length > 0);
12706         },
12707
12708         /**
12709          * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
12710          * @param {HTMLElement[]} el An array of elements to filter
12711          * @param {String} selector The simple selector to test
12712          * @param {Boolean} nonMatches If true, it returns the elements that DON'T match
12713          * the selector instead of the ones that match
12714          * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
12715          * no matches, and empty Array is returned.
12716          */
12717         filter : function(els, ss, nonMatches){
12718             ss = ss.replace(trimRe, "");
12719             if(!simpleCache[ss]){
12720                 simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");
12721             }
12722             var result = simpleCache[ss](els);
12723             return nonMatches ? quickDiff(result, els) : result;
12724         },
12725
12726         /**
12727          * Collection of matching regular expressions and code snippets.
12728          * Each capture group within () will be replace the {} in the select
12729          * statement as specified by their index.
12730          */
12731         matchers : [{
12732                 re: /^\.([\w-]+)/,
12733                 select: 'n = byClassName(n, " {1} ");'
12734             }, {
12735                 re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
12736                 select: 'n = byPseudo(n, "{1}", "{2}");'
12737             },{
12738                 re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
12739                 select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
12740             }, {
12741                 re: /^#([\w-]+)/,
12742                 select: 'n = byId(n, "{1}");'
12743             },{
12744                 re: /^@([\w-]+)/,
12745                 select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
12746             }
12747         ],
12748
12749         /**
12750          * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.
12751          * 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;.
12752          */
12753         operators : {
12754             "=" : function(a, v){
12755                 return a == v;
12756             },
12757             "!=" : function(a, v){
12758                 return a != v;
12759             },
12760             "^=" : function(a, v){
12761                 return a && a.substr(0, v.length) == v;
12762             },
12763             "$=" : function(a, v){
12764                 return a && a.substr(a.length-v.length) == v;
12765             },
12766             "*=" : function(a, v){
12767                 return a && a.indexOf(v) !== -1;
12768             },
12769             "%=" : function(a, v){
12770                 return (a % v) == 0;
12771             },
12772             "|=" : function(a, v){
12773                 return a && (a == v || a.substr(0, v.length+1) == v+'-');
12774             },
12775             "~=" : function(a, v){
12776                 return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
12777             }
12778         },
12779
12780         /**
12781 Object hash of "pseudo class" filter functions which are used when filtering selections.
12782 Each function is passed two parameters:
12783
12784 - **c** : Array
12785     An Array of DOM elements to filter.
12786
12787 - **v** : String
12788     The argument (if any) supplied in the selector.
12789
12790 A filter function returns an Array of DOM elements which conform to the pseudo class.
12791 In addition to the provided pseudo classes listed above such as `first-child` and `nth-child`,
12792 developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.
12793
12794 For example, to filter `a` elements to only return links to __external__ resources:
12795
12796     Ext.DomQuery.pseudos.external = function(c, v){
12797         var r = [], ri = -1;
12798         for(var i = 0, ci; ci = c[i]; i++){
12799             // Include in result set only if it's a link to an external resource
12800             if(ci.hostname != location.hostname){
12801                 r[++ri] = ci;
12802             }
12803         }
12804         return r;
12805     };
12806
12807 Then external links could be gathered with the following statement:
12808
12809     var externalLinks = Ext.select("a:external");
12810
12811         * @markdown
12812         */
12813         pseudos : {
12814             "first-child" : function(c){
12815                 var r = [], ri = -1, n;
12816                 for(var i = 0, ci; ci = n = c[i]; i++){
12817                     while((n = n.previousSibling) && n.nodeType != 1);
12818                     if(!n){
12819                         r[++ri] = ci;
12820                     }
12821                 }
12822                 return r;
12823             },
12824
12825             "last-child" : function(c){
12826                 var r = [], ri = -1, n;
12827                 for(var i = 0, ci; ci = n = c[i]; i++){
12828                     while((n = n.nextSibling) && n.nodeType != 1);
12829                     if(!n){
12830                         r[++ri] = ci;
12831                     }
12832                 }
12833                 return r;
12834             },
12835
12836             "nth-child" : function(c, a) {
12837                 var r = [], ri = -1,
12838                     m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
12839                     f = (m[1] || 1) - 0, l = m[2] - 0;
12840                 for(var i = 0, n; n = c[i]; i++){
12841                     var pn = n.parentNode;
12842                     if (batch != pn._batch) {
12843                         var j = 0;
12844                         for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
12845                             if(cn.nodeType == 1){
12846                                cn.nodeIndex = ++j;
12847                             }
12848                         }
12849                         pn._batch = batch;
12850                     }
12851                     if (f == 1) {
12852                         if (l == 0 || n.nodeIndex == l){
12853                             r[++ri] = n;
12854                         }
12855                     } else if ((n.nodeIndex + l) % f == 0){
12856                         r[++ri] = n;
12857                     }
12858                 }
12859
12860                 return r;
12861             },
12862
12863             "only-child" : function(c){
12864                 var r = [], ri = -1;;
12865                 for(var i = 0, ci; ci = c[i]; i++){
12866                     if(!prev(ci) && !next(ci)){
12867                         r[++ri] = ci;
12868                     }
12869                 }
12870                 return r;
12871             },
12872
12873             "empty" : function(c){
12874                 var r = [], ri = -1;
12875                 for(var i = 0, ci; ci = c[i]; i++){
12876                     var cns = ci.childNodes, j = 0, cn, empty = true;
12877                     while(cn = cns[j]){
12878                         ++j;
12879                         if(cn.nodeType == 1 || cn.nodeType == 3){
12880                             empty = false;
12881                             break;
12882                         }
12883                     }
12884                     if(empty){
12885                         r[++ri] = ci;
12886                     }
12887                 }
12888                 return r;
12889             },
12890
12891             "contains" : function(c, v){
12892                 var r = [], ri = -1;
12893                 for(var i = 0, ci; ci = c[i]; i++){
12894                     if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
12895                         r[++ri] = ci;
12896                     }
12897                 }
12898                 return r;
12899             },
12900
12901             "nodeValue" : function(c, v){
12902                 var r = [], ri = -1;
12903                 for(var i = 0, ci; ci = c[i]; i++){
12904                     if(ci.firstChild && ci.firstChild.nodeValue == v){
12905                         r[++ri] = ci;
12906                     }
12907                 }
12908                 return r;
12909             },
12910
12911             "checked" : function(c){
12912                 var r = [], ri = -1;
12913                 for(var i = 0, ci; ci = c[i]; i++){
12914                     if(ci.checked == true){
12915                         r[++ri] = ci;
12916                     }
12917                 }
12918                 return r;
12919             },
12920
12921             "not" : function(c, ss){
12922                 return Ext.DomQuery.filter(c, ss, true);
12923             },
12924
12925             "any" : function(c, selectors){
12926                 var ss = selectors.split('|'),
12927                     r = [], ri = -1, s;
12928                 for(var i = 0, ci; ci = c[i]; i++){
12929                     for(var j = 0; s = ss[j]; j++){
12930                         if(Ext.DomQuery.is(ci, s)){
12931                             r[++ri] = ci;
12932                             break;
12933                         }
12934                     }
12935                 }
12936                 return r;
12937             },
12938
12939             "odd" : function(c){
12940                 return this["nth-child"](c, "odd");
12941             },
12942
12943             "even" : function(c){
12944                 return this["nth-child"](c, "even");
12945             },
12946
12947             "nth" : function(c, a){
12948                 return c[a-1] || [];
12949             },
12950
12951             "first" : function(c){
12952                 return c[0] || [];
12953             },
12954
12955             "last" : function(c){
12956                 return c[c.length-1] || [];
12957             },
12958
12959             "has" : function(c, ss){
12960                 var s = Ext.DomQuery.select,
12961                     r = [], ri = -1;
12962                 for(var i = 0, ci; ci = c[i]; i++){
12963                     if(s(ss, ci).length > 0){
12964                         r[++ri] = ci;
12965                     }
12966                 }
12967                 return r;
12968             },
12969
12970             "next" : function(c, ss){
12971                 var is = Ext.DomQuery.is,
12972                     r = [], ri = -1;
12973                 for(var i = 0, ci; ci = c[i]; i++){
12974                     var n = next(ci);
12975                     if(n && is(n, ss)){
12976                         r[++ri] = ci;
12977                     }
12978                 }
12979                 return r;
12980             },
12981
12982             "prev" : function(c, ss){
12983                 var is = Ext.DomQuery.is,
12984                     r = [], ri = -1;
12985                 for(var i = 0, ci; ci = c[i]; i++){
12986                     var n = prev(ci);
12987                     if(n && is(n, ss)){
12988                         r[++ri] = ci;
12989                     }
12990                 }
12991                 return r;
12992             }
12993         }
12994     };
12995 }();
12996
12997 /**
12998  * Shorthand of {@link Ext.DomQuery#select}
12999  * @member Ext
13000  * @method query
13001  * @alias Ext.DomQuery#select
13002  */
13003 Ext.query = Ext.DomQuery.select;
13004
13005 /**
13006  * @class Ext.Element
13007  * @alternateClassName Ext.core.Element
13008  *
13009  * Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.
13010  *
13011  * All instances of this class inherit the methods of {@link Ext.fx.Anim} making visual effects easily available to all
13012  * DOM elements.
13013  *
13014  * Note that the events documented in this class are not Ext events, they encapsulate browser events. Some older browsers
13015  * may not support the full range of events. Which events are supported is beyond the control of Ext JS.
13016  *
13017  * Usage:
13018  *
13019  *     // by id
13020  *     var el = Ext.get("my-div");
13021  *
13022  *     // by DOM element reference
13023  *     var el = Ext.get(myDivElement);
13024  *
13025  * # Animations
13026  *
13027  * When an element is manipulated, by default there is no animation.
13028  *
13029  *     var el = Ext.get("my-div");
13030  *
13031  *     // no animation
13032  *     el.setWidth(100);
13033  *
13034  * Many of the functions for manipulating an element have an optional "animate" parameter. This parameter can be
13035  * specified as boolean (true) for default animation effects.
13036  *
13037  *     // default animation
13038  *     el.setWidth(100, true);
13039  *
13040  * To configure the effects, an object literal with animation options to use as the Element animation configuration
13041  * object can also be specified. Note that the supported Element animation configuration options are a subset of the
13042  * {@link Ext.fx.Anim} animation options specific to Fx effects. The supported Element animation configuration options
13043  * are:
13044  *
13045  *     Option    Default   Description
13046  *     --------- --------  ---------------------------------------------
13047  *     {@link Ext.fx.Anim#duration duration}  .35       The duration of the animation in seconds
13048  *     {@link Ext.fx.Anim#easing easing}    easeOut   The easing method
13049  *     {@link Ext.fx.Anim#callback callback}  none      A function to execute when the anim completes
13050  *     {@link Ext.fx.Anim#scope scope}     this      The scope (this) of the callback function
13051  *
13052  * Usage:
13053  *
13054  *     // Element animation options object
13055  *     var opt = {
13056  *         {@link Ext.fx.Anim#duration duration}: 1,
13057  *         {@link Ext.fx.Anim#easing easing}: 'elasticIn',
13058  *         {@link Ext.fx.Anim#callback callback}: this.foo,
13059  *         {@link Ext.fx.Anim#scope scope}: this
13060  *     };
13061  *     // animation with some options set
13062  *     el.setWidth(100, opt);
13063  *
13064  * The Element animation object being used for the animation will be set on the options object as "anim", which allows
13065  * you to stop or manipulate the animation. Here is an example:
13066  *
13067  *     // using the "anim" property to get the Anim object
13068  *     if(opt.anim.isAnimated()){
13069  *         opt.anim.stop();
13070  *     }
13071  *
13072  * # Composite (Collections of) Elements
13073  *
13074  * For working with collections of Elements, see {@link Ext.CompositeElement}
13075  *
13076  * @constructor
13077  * Creates new Element directly.
13078  * @param {String/HTMLElement} element
13079  * @param {Boolean} forceNew (optional) By default the constructor checks to see if there is already an instance of this
13080  * element in the cache and if there is it returns the same instance. This will skip that check (useful for extending
13081  * this class).
13082  * @return {Object}
13083  */
13084  (function() {
13085     var DOC = document,
13086         EC = Ext.cache;
13087
13088     Ext.Element = Ext.core.Element = function(element, forceNew) {
13089         var dom = typeof element == "string" ? DOC.getElementById(element) : element,
13090         id;
13091
13092         if (!dom) {
13093             return null;
13094         }
13095
13096         id = dom.id;
13097
13098         if (!forceNew && id && EC[id]) {
13099             // element object already exists
13100             return EC[id].el;
13101         }
13102
13103         /**
13104          * @property {HTMLElement} dom
13105          * The DOM element
13106          */
13107         this.dom = dom;
13108
13109         /**
13110          * @property {String} id
13111          * The DOM element ID
13112          */
13113         this.id = id || Ext.id(dom);
13114     };
13115
13116     var DH = Ext.DomHelper,
13117     El = Ext.Element;
13118
13119
13120     El.prototype = {
13121         /**
13122          * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
13123          * @param {Object} o The object with the attributes
13124          * @param {Boolean} useSet (optional) false to override the default setAttribute to use expandos.
13125          * @return {Ext.Element} this
13126          */
13127         set: function(o, useSet) {
13128             var el = this.dom,
13129                 attr,
13130                 val;
13131             useSet = (useSet !== false) && !!el.setAttribute;
13132
13133             for (attr in o) {
13134                 if (o.hasOwnProperty(attr)) {
13135                     val = o[attr];
13136                     if (attr == 'style') {
13137                         DH.applyStyles(el, val);
13138                     } else if (attr == 'cls') {
13139                         el.className = val;
13140                     } else if (useSet) {
13141                         el.setAttribute(attr, val);
13142                     } else {
13143                         el[attr] = val;
13144                     }
13145                 }
13146             }
13147             return this;
13148         },
13149
13150         //  Mouse events
13151         /**
13152          * @event click
13153          * Fires when a mouse click is detected within the element.
13154          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13155          * @param {HTMLElement} t The target of the event.
13156          */
13157         /**
13158          * @event contextmenu
13159          * Fires when a right click is detected within the element.
13160          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13161          * @param {HTMLElement} t The target of the event.
13162          */
13163         /**
13164          * @event dblclick
13165          * Fires when a mouse double click is detected within the element.
13166          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13167          * @param {HTMLElement} t The target of the event.
13168          */
13169         /**
13170          * @event mousedown
13171          * Fires when a mousedown is detected within the element.
13172          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13173          * @param {HTMLElement} t The target of the event.
13174          */
13175         /**
13176          * @event mouseup
13177          * Fires when a mouseup is detected within the element.
13178          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13179          * @param {HTMLElement} t The target of the event.
13180          */
13181         /**
13182          * @event mouseover
13183          * Fires when a mouseover is detected within the element.
13184          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13185          * @param {HTMLElement} t The target of the event.
13186          */
13187         /**
13188          * @event mousemove
13189          * Fires when a mousemove is detected with the element.
13190          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13191          * @param {HTMLElement} t The target of the event.
13192          */
13193         /**
13194          * @event mouseout
13195          * Fires when a mouseout is detected with the element.
13196          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13197          * @param {HTMLElement} t The target of the event.
13198          */
13199         /**
13200          * @event mouseenter
13201          * Fires when the mouse enters the element.
13202          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13203          * @param {HTMLElement} t The target of the event.
13204          */
13205         /**
13206          * @event mouseleave
13207          * Fires when the mouse leaves the element.
13208          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13209          * @param {HTMLElement} t The target of the event.
13210          */
13211
13212         //  Keyboard events
13213         /**
13214          * @event keypress
13215          * Fires when a keypress is detected within the element.
13216          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13217          * @param {HTMLElement} t The target of the event.
13218          */
13219         /**
13220          * @event keydown
13221          * Fires when a keydown is detected within the element.
13222          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13223          * @param {HTMLElement} t The target of the event.
13224          */
13225         /**
13226          * @event keyup
13227          * Fires when a keyup is detected within the element.
13228          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13229          * @param {HTMLElement} t The target of the event.
13230          */
13231
13232
13233         //  HTML frame/object events
13234         /**
13235          * @event load
13236          * Fires when the user agent finishes loading all content within the element. Only supported by window, frames,
13237          * objects and images.
13238          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13239          * @param {HTMLElement} t The target of the event.
13240          */
13241         /**
13242          * @event unload
13243          * Fires when the user agent removes all content from a window or frame. For elements, it fires when the target
13244          * element or any of its content has been removed.
13245          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13246          * @param {HTMLElement} t The target of the event.
13247          */
13248         /**
13249          * @event abort
13250          * Fires when an object/image is stopped from loading before completely loaded.
13251          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13252          * @param {HTMLElement} t The target of the event.
13253          */
13254         /**
13255          * @event error
13256          * Fires when an object/image/frame cannot be loaded properly.
13257          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13258          * @param {HTMLElement} t The target of the event.
13259          */
13260         /**
13261          * @event resize
13262          * Fires when a document view is resized.
13263          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13264          * @param {HTMLElement} t The target of the event.
13265          */
13266         /**
13267          * @event scroll
13268          * Fires when a document view is scrolled.
13269          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13270          * @param {HTMLElement} t The target of the event.
13271          */
13272
13273         //  Form events
13274         /**
13275          * @event select
13276          * Fires when a user selects some text in a text field, including input and textarea.
13277          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13278          * @param {HTMLElement} t The target of the event.
13279          */
13280         /**
13281          * @event change
13282          * Fires when a control loses the input focus and its value has been modified since gaining focus.
13283          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13284          * @param {HTMLElement} t The target of the event.
13285          */
13286         /**
13287          * @event submit
13288          * Fires when a form is submitted.
13289          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13290          * @param {HTMLElement} t The target of the event.
13291          */
13292         /**
13293          * @event reset
13294          * Fires when a form is reset.
13295          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13296          * @param {HTMLElement} t The target of the event.
13297          */
13298         /**
13299          * @event focus
13300          * Fires when an element receives focus either via the pointing device or by tab navigation.
13301          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13302          * @param {HTMLElement} t The target of the event.
13303          */
13304         /**
13305          * @event blur
13306          * Fires when an element loses focus either via the pointing device or by tabbing navigation.
13307          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13308          * @param {HTMLElement} t The target of the event.
13309          */
13310
13311         //  User Interface events
13312         /**
13313          * @event DOMFocusIn
13314          * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
13315          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13316          * @param {HTMLElement} t The target of the event.
13317          */
13318         /**
13319          * @event DOMFocusOut
13320          * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
13321          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13322          * @param {HTMLElement} t The target of the event.
13323          */
13324         /**
13325          * @event DOMActivate
13326          * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
13327          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13328          * @param {HTMLElement} t The target of the event.
13329          */
13330
13331         //  DOM Mutation events
13332         /**
13333          * @event DOMSubtreeModified
13334          * Where supported. Fires when the subtree is modified.
13335          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13336          * @param {HTMLElement} t The target of the event.
13337          */
13338         /**
13339          * @event DOMNodeInserted
13340          * Where supported. Fires when a node has been added as a child of another node.
13341          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13342          * @param {HTMLElement} t The target of the event.
13343          */
13344         /**
13345          * @event DOMNodeRemoved
13346          * Where supported. Fires when a descendant node of the element is removed.
13347          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13348          * @param {HTMLElement} t The target of the event.
13349          */
13350         /**
13351          * @event DOMNodeRemovedFromDocument
13352          * Where supported. Fires when a node is being removed from a document.
13353          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13354          * @param {HTMLElement} t The target of the event.
13355          */
13356         /**
13357          * @event DOMNodeInsertedIntoDocument
13358          * Where supported. Fires when a node is being inserted into a document.
13359          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13360          * @param {HTMLElement} t The target of the event.
13361          */
13362         /**
13363          * @event DOMAttrModified
13364          * Where supported. Fires when an attribute has been modified.
13365          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13366          * @param {HTMLElement} t The target of the event.
13367          */
13368         /**
13369          * @event DOMCharacterDataModified
13370          * Where supported. Fires when the character data has been modified.
13371          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13372          * @param {HTMLElement} t The target of the event.
13373          */
13374
13375         /**
13376          * @property {String} defaultUnit
13377          * The default unit to append to CSS values where a unit isn't provided.
13378          */
13379         defaultUnit: "px",
13380
13381         /**
13382          * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
13383          * @param {String} selector The simple selector to test
13384          * @return {Boolean} True if this element matches the selector, else false
13385          */
13386         is: function(simpleSelector) {
13387             return Ext.DomQuery.is(this.dom, simpleSelector);
13388         },
13389
13390         /**
13391          * Tries to focus the element. Any exceptions are caught and ignored.
13392          * @param {Number} defer (optional) Milliseconds to defer the focus
13393          * @return {Ext.Element} this
13394          */
13395         focus: function(defer,
13396                         /* private */
13397                         dom) {
13398             var me = this;
13399             dom = dom || me.dom;
13400             try {
13401                 if (Number(defer)) {
13402                     Ext.defer(me.focus, defer, null, [null, dom]);
13403                 } else {
13404                     dom.focus();
13405                 }
13406             } catch(e) {}
13407             return me;
13408         },
13409
13410         /**
13411          * Tries to blur the element. Any exceptions are caught and ignored.
13412          * @return {Ext.Element} this
13413          */
13414         blur: function() {
13415             try {
13416                 this.dom.blur();
13417             } catch(e) {}
13418             return this;
13419         },
13420
13421         /**
13422          * Returns the value of the "value" attribute
13423          * @param {Boolean} asNumber true to parse the value as a number
13424          * @return {String/Number}
13425          */
13426         getValue: function(asNumber) {
13427             var val = this.dom.value;
13428             return asNumber ? parseInt(val, 10) : val;
13429         },
13430
13431         /**
13432          * Appends an event handler to this element.
13433          *
13434          * @param {String} eventName The name of event to handle.
13435          *
13436          * @param {Function} fn The handler function the event invokes. This function is passed the following parameters:
13437          *
13438          * - **evt** : EventObject
13439          *
13440          *   The {@link Ext.EventObject EventObject} describing the event.
13441          *
13442          * - **el** : HtmlElement
13443          *
13444          *   The DOM element which was the target of the event. Note that this may be filtered by using the delegate option.
13445          *
13446          * - **o** : Object
13447          *
13448          *   The options object from the addListener call.
13449          *
13450          * @param {Object} scope (optional) The scope (**this** reference) in which the handler function is executed. **If
13451          * omitted, defaults to this Element.**
13452          *
13453          * @param {Object} options (optional) An object containing handler configuration properties. This may contain any of
13454          * the following properties:
13455          *
13456          * - **scope** Object :
13457          *
13458          *   The scope (**this** reference) in which the handler function is executed. **If omitted, defaults to this
13459          *   Element.**
13460          *
13461          * - **delegate** String:
13462          *
13463          *   A simple selector to filter the target or look for a descendant of the target. See below for additional details.
13464          *
13465          * - **stopEvent** Boolean:
13466          *
13467          *   True to stop the event. That is stop propagation, and prevent the default action.
13468          *
13469          * - **preventDefault** Boolean:
13470          *
13471          *   True to prevent the default action
13472          *
13473          * - **stopPropagation** Boolean:
13474          *
13475          *   True to prevent event propagation
13476          *
13477          * - **normalized** Boolean:
13478          *
13479          *   False to pass a browser event to the handler function instead of an Ext.EventObject
13480          *
13481          * - **target** Ext.Element:
13482          *
13483          *   Only call the handler if the event was fired on the target Element, _not_ if the event was bubbled up from a
13484          *   child node.
13485          *
13486          * - **delay** Number:
13487          *
13488          *   The number of milliseconds to delay the invocation of the handler after the event fires.
13489          *
13490          * - **single** Boolean:
13491          *
13492          *   True to add a handler to handle just the next firing of the event, and then remove itself.
13493          *
13494          * - **buffer** Number:
13495          *
13496          *   Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
13497          *   milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
13498          *   handler is scheduled in its place.
13499          *
13500          * **Combining Options**
13501          *
13502          * In the following examples, the shorthand form {@link #on} is used rather than the more verbose addListener. The
13503          * two are equivalent. Using the options argument, it is possible to combine different types of listeners:
13504          *
13505          * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the options
13506          * object. The options object is available as the third parameter in the handler function.
13507          *
13508          * Code:
13509          *
13510          *     el.on('click', this.onClick, this, {
13511          *         single: true,
13512          *         delay: 100,
13513          *         stopEvent : true,
13514          *         forumId: 4
13515          *     });
13516          *
13517          * **Attaching multiple handlers in 1 call**
13518          *
13519          * The method also allows for a single argument to be passed which is a config object containing properties which
13520          * specify multiple handlers.
13521          *
13522          * Code:
13523          *
13524          *     el.on({
13525          *         'click' : {
13526          *             fn: this.onClick,
13527          *             scope: this,
13528          *             delay: 100
13529          *         },
13530          *         'mouseover' : {
13531          *             fn: this.onMouseOver,
13532          *             scope: this
13533          *         },
13534          *         'mouseout' : {
13535          *             fn: this.onMouseOut,
13536          *             scope: this
13537          *         }
13538          *     });
13539          *
13540          * Or a shorthand syntax:
13541          *
13542          * Code:
13543          *
13544          *     el.on({
13545          *         'click' : this.onClick,
13546          *         'mouseover' : this.onMouseOver,
13547          *         'mouseout' : this.onMouseOut,
13548          *         scope: this
13549          *     });
13550          *
13551          * **delegate**
13552          *
13553          * This is a configuration option that you can pass along when registering a handler for an event to assist with
13554          * event delegation. Event delegation is a technique that is used to reduce memory consumption and prevent exposure
13555          * to memory-leaks. By registering an event for a container element as opposed to each element within a container.
13556          * By setting this configuration option to a simple selector, the target element will be filtered to look for a
13557          * descendant of the target. For example:
13558          *
13559          *     // using this markup:
13560          *     <div id='elId'>
13561          *         <p id='p1'>paragraph one</p>
13562          *         <p id='p2' class='clickable'>paragraph two</p>
13563          *         <p id='p3'>paragraph three</p>
13564          *     </div>
13565          *
13566          *     // utilize event delegation to registering just one handler on the container element:
13567          *     el = Ext.get('elId');
13568          *     el.on(
13569          *         'click',
13570          *         function(e,t) {
13571          *             // handle click
13572          *             console.info(t.id); // 'p2'
13573          *         },
13574          *         this,
13575          *         {
13576          *             // filter the target element to be a descendant with the class 'clickable'
13577          *             delegate: '.clickable'
13578          *         }
13579          *     );
13580          *
13581          * @return {Ext.Element} this
13582          */
13583         addListener: function(eventName, fn, scope, options) {
13584             Ext.EventManager.on(this.dom, eventName, fn, scope || this, options);
13585             return this;
13586         },
13587
13588         /**
13589          * Removes an event handler from this element.
13590          *
13591          * **Note**: if a *scope* was explicitly specified when {@link #addListener adding} the listener,
13592          * the same scope must be specified here.
13593          *
13594          * Example:
13595          *
13596          *     el.removeListener('click', this.handlerFn);
13597          *     // or
13598          *     el.un('click', this.handlerFn);
13599          *
13600          * @param {String} eventName The name of the event from which to remove the handler.
13601          * @param {Function} fn The handler function to remove. **This must be a reference to the function passed into the
13602          * {@link #addListener} call.**
13603          * @param {Object} scope If a scope (**this** reference) was specified when the listener was added, then this must
13604          * refer to the same object.
13605          * @return {Ext.Element} this
13606          */
13607         removeListener: function(eventName, fn, scope) {
13608             Ext.EventManager.un(this.dom, eventName, fn, scope || this);
13609             return this;
13610         },
13611
13612         /**
13613          * Removes all previous added listeners from this element
13614          * @return {Ext.Element} this
13615          */
13616         removeAllListeners: function() {
13617             Ext.EventManager.removeAll(this.dom);
13618             return this;
13619         },
13620
13621         /**
13622          * Recursively removes all previous added listeners from this element and its children
13623          * @return {Ext.Element} this
13624          */
13625         purgeAllListeners: function() {
13626             Ext.EventManager.purgeElement(this);
13627             return this;
13628         },
13629
13630         /**
13631          * Test if size has a unit, otherwise appends the passed unit string, or the default for this Element.
13632          * @param size {Mixed} The size to set
13633          * @param units {String} The units to append to a numeric size value
13634          * @private
13635          */
13636         addUnits: function(size, units) {
13637
13638             // Most common case first: Size is set to a number
13639             if (Ext.isNumber(size)) {
13640                 return size + (units || this.defaultUnit || 'px');
13641             }
13642
13643             // Size set to a value which means "auto"
13644             if (size === "" || size == "auto" || size == null) {
13645                 return size || '';
13646             }
13647
13648             // Otherwise, warn if it's not a valid CSS measurement
13649             if (!unitPattern.test(size)) {
13650                 if (Ext.isDefined(Ext.global.console)) {
13651                     Ext.global.console.warn("Warning, size detected as NaN on Element.addUnits.");
13652                 }
13653                 return size || '';
13654             }
13655             return size;
13656         },
13657
13658         /**
13659          * Tests various css rules/browsers to determine if this element uses a border box
13660          * @return {Boolean}
13661          */
13662         isBorderBox: function() {
13663             return Ext.isBorderBox || noBoxAdjust[(this.dom.tagName || "").toLowerCase()];
13664         },
13665
13666         /**
13667          * Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode
13668          * Ext.removeNode}
13669          */
13670         remove: function() {
13671             var me = this,
13672             dom = me.dom;
13673
13674             if (dom) {
13675                 delete me.dom;
13676                 Ext.removeNode(dom);
13677             }
13678         },
13679
13680         /**
13681          * Sets up event handlers to call the passed functions when the mouse is moved into and out of the Element.
13682          * @param {Function} overFn The function to call when the mouse enters the Element.
13683          * @param {Function} outFn The function to call when the mouse leaves the Element.
13684          * @param {Object} scope (optional) The scope (`this` reference) in which the functions are executed. Defaults
13685          * to the Element's DOM element.
13686          * @param {Object} options (optional) Options for the listener. See {@link Ext.util.Observable#addListener the
13687          * options parameter}.
13688          * @return {Ext.Element} this
13689          */
13690         hover: function(overFn, outFn, scope, options) {
13691             var me = this;
13692             me.on('mouseenter', overFn, scope || me.dom, options);
13693             me.on('mouseleave', outFn, scope || me.dom, options);
13694             return me;
13695         },
13696
13697         /**
13698          * Returns true if this element is an ancestor of the passed element
13699          * @param {HTMLElement/String} el The element to check
13700          * @return {Boolean} True if this element is an ancestor of el, else false
13701          */
13702         contains: function(el) {
13703             return ! el ? false: Ext.Element.isAncestor(this.dom, el.dom ? el.dom: el);
13704         },
13705
13706         /**
13707          * Returns the value of a namespaced attribute from the element's underlying DOM node.
13708          * @param {String} namespace The namespace in which to look for the attribute
13709          * @param {String} name The attribute name
13710          * @return {String} The attribute value
13711          */
13712         getAttributeNS: function(ns, name) {
13713             return this.getAttribute(name, ns);
13714         },
13715
13716         /**
13717          * Returns the value of an attribute from the element's underlying DOM node.
13718          * @param {String} name The attribute name
13719          * @param {String} namespace (optional) The namespace in which to look for the attribute
13720          * @return {String} The attribute value
13721          * @method
13722          */
13723         getAttribute: (Ext.isIE && !(Ext.isIE9 && document.documentMode === 9)) ?
13724         function(name, ns) {
13725             var d = this.dom,
13726             type;
13727             if(ns) {
13728                 type = typeof d[ns + ":" + name];
13729                 if (type != 'undefined' && type != 'unknown') {
13730                     return d[ns + ":" + name] || null;
13731                 }
13732                 return null;
13733             }
13734             if (name === "for") {
13735                 name = "htmlFor";
13736             }
13737             return d[name] || null;
13738         }: function(name, ns) {
13739             var d = this.dom;
13740             if (ns) {
13741                return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name);
13742             }
13743             return  d.getAttribute(name) || d[name] || null;
13744         },
13745
13746         /**
13747          * Update the innerHTML of this element
13748          * @param {String} html The new HTML
13749          * @return {Ext.Element} this
13750          */
13751         update: function(html) {
13752             if (this.dom) {
13753                 this.dom.innerHTML = html;
13754             }
13755             return this;
13756         }
13757     };
13758
13759     var ep = El.prototype;
13760
13761     El.addMethods = function(o) {
13762         Ext.apply(ep, o);
13763     };
13764
13765     /**
13766      * @method
13767      * @alias Ext.Element#addListener
13768      * Shorthand for {@link #addListener}.
13769      */
13770     ep.on = ep.addListener;
13771
13772     /**
13773      * @method
13774      * @alias Ext.Element#removeListener
13775      * Shorthand for {@link #removeListener}.
13776      */
13777     ep.un = ep.removeListener;
13778
13779     /**
13780      * @method
13781      * @alias Ext.Element#removeAllListeners
13782      * Alias for {@link #removeAllListeners}.
13783      */
13784     ep.clearListeners = ep.removeAllListeners;
13785
13786     /**
13787      * @method destroy
13788      * @member Ext.Element
13789      * Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode
13790      * Ext.removeNode}. Alias to {@link #remove}.
13791      */
13792     ep.destroy = ep.remove;
13793
13794     /**
13795      * @property {Boolean} autoBoxAdjust
13796      * true to automatically adjust width and height settings for box-model issues (default to true)
13797      */
13798     ep.autoBoxAdjust = true;
13799
13800     // private
13801     var unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
13802     docEl;
13803
13804     /**
13805      * Retrieves Ext.Element objects. {@link Ext#get} is an alias for {@link Ext.Element#get}.
13806      *
13807      * **This method does not retrieve {@link Ext.Component Component}s.** This method retrieves Ext.Element
13808      * objects which encapsulate DOM elements. To retrieve a Component by its ID, use {@link Ext.ComponentManager#get}.
13809      *
13810      * Uses simple caching to consistently return the same object. Automatically fixes if an object was recreated with
13811      * the same id via AJAX or DOM.
13812      *
13813      * @param {String/HTMLElement/Ext.Element} el The id of the node, a DOM Node or an existing Element.
13814      * @return {Ext.Element} The Element object (or null if no matching element was found)
13815      * @static
13816      */
13817     El.get = function(el) {
13818         var ex,
13819         elm,
13820         id;
13821         if (!el) {
13822             return null;
13823         }
13824         if (typeof el == "string") {
13825             // element id
13826             if (! (elm = DOC.getElementById(el))) {
13827                 return null;
13828             }
13829             if (EC[el] && EC[el].el) {
13830                 ex = EC[el].el;
13831                 ex.dom = elm;
13832             } else {
13833                 ex = El.addToCache(new El(elm));
13834             }
13835             return ex;
13836         } else if (el.tagName) {
13837             // dom element
13838             if (! (id = el.id)) {
13839                 id = Ext.id(el);
13840             }
13841             if (EC[id] && EC[id].el) {
13842                 ex = EC[id].el;
13843                 ex.dom = el;
13844             } else {
13845                 ex = El.addToCache(new El(el));
13846             }
13847             return ex;
13848         } else if (el instanceof El) {
13849             if (el != docEl) {
13850                 // refresh dom element in case no longer valid,
13851                 // catch case where it hasn't been appended
13852                 // If an el instance is passed, don't pass to getElementById without some kind of id
13853                 if (Ext.isIE && (el.id == undefined || el.id == '')) {
13854                     el.dom = el.dom;
13855                 } else {
13856                     el.dom = DOC.getElementById(el.id) || el.dom;
13857                 }
13858             }
13859             return el;
13860         } else if (el.isComposite) {
13861             return el;
13862         } else if (Ext.isArray(el)) {
13863             return El.select(el);
13864         } else if (el == DOC) {
13865             // create a bogus element object representing the document object
13866             if (!docEl) {
13867                 var f = function() {};
13868                 f.prototype = El.prototype;
13869                 docEl = new f();
13870                 docEl.dom = DOC;
13871             }
13872             return docEl;
13873         }
13874         return null;
13875     };
13876
13877     /**
13878      * Retrieves Ext.Element objects like {@link Ext#get} but is optimized for sub-elements.
13879      * This is helpful for performance, because in IE (prior to IE 9), `getElementById` uses
13880      * an non-optimized search. In those browsers, starting the search for an element with a
13881      * matching ID at a parent of that element will greatly speed up the process.
13882      *
13883      * Unlike {@link Ext#get}, this method only accepts ID's. If the ID is not a child of
13884      * this element, it will still be found if it exists in the document, but will be slower
13885      * than calling {@link Ext#get} directly.
13886      *
13887      * @param {String} id The id of the element to get.
13888      * @return {Ext.Element} The Element object (or null if no matching element was found)
13889      * @member Ext.Element
13890      * @method getById
13891      * @markdown
13892      */
13893     ep.getById = (!Ext.isIE6 && !Ext.isIE7 && !Ext.isIE8) ? El.get :
13894         function (id) {
13895             var dom = this.dom,
13896                 cached, el, ret;
13897
13898             if (dom) {
13899                 el = dom.all[id];
13900                 if (el) {
13901                     // calling El.get here is a real hit (2x slower) because it has to
13902                     // redetermine that we are giving it a dom el.
13903                     cached = EC[id];
13904                     if (cached && cached.el) {
13905                         ret = cached.el;
13906                         ret.dom = el;
13907                     } else {
13908                         ret = El.addToCache(new El(el));
13909                     }
13910                     return ret;
13911                 }
13912             }
13913
13914             return El.get(id);
13915         };
13916
13917     El.addToCache = function(el, id) {
13918         if (el) {
13919             id = id || el.id;
13920             EC[id] = {
13921                 el: el,
13922                 data: {},
13923                 events: {}
13924             };
13925         }
13926         return el;
13927     };
13928
13929     // private method for getting and setting element data
13930     El.data = function(el, key, value) {
13931         el = El.get(el);
13932         if (!el) {
13933             return null;
13934         }
13935         var c = EC[el.id].data;
13936         if (arguments.length == 2) {
13937             return c[key];
13938         } else {
13939             return (c[key] = value);
13940         }
13941     };
13942
13943     // private
13944     // Garbage collection - uncache elements/purge listeners on orphaned elements
13945     // so we don't hold a reference and cause the browser to retain them
13946     function garbageCollect() {
13947         if (!Ext.enableGarbageCollector) {
13948             clearInterval(El.collectorThreadId);
13949         } else {
13950             var eid,
13951             el,
13952             d,
13953             o;
13954
13955             for (eid in EC) {
13956                 if (!EC.hasOwnProperty(eid)) {
13957                     continue;
13958                 }
13959                 o = EC[eid];
13960                 if (o.skipGarbageCollection) {
13961                     continue;
13962                 }
13963                 el = o.el;
13964                 d = el.dom;
13965                 // -------------------------------------------------------
13966                 // Determining what is garbage:
13967                 // -------------------------------------------------------
13968                 // !d
13969                 // dom node is null, definitely garbage
13970                 // -------------------------------------------------------
13971                 // !d.parentNode
13972                 // no parentNode == direct orphan, definitely garbage
13973                 // -------------------------------------------------------
13974                 // !d.offsetParent && !document.getElementById(eid)
13975                 // display none elements have no offsetParent so we will
13976                 // also try to look it up by it's id. However, check
13977                 // offsetParent first so we don't do unneeded lookups.
13978                 // This enables collection of elements that are not orphans
13979                 // directly, but somewhere up the line they have an orphan
13980                 // parent.
13981                 // -------------------------------------------------------
13982                 if (!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))) {
13983                     if (d && Ext.enableListenerCollection) {
13984                         Ext.EventManager.removeAll(d);
13985                     }
13986                     delete EC[eid];
13987                 }
13988             }
13989             // Cleanup IE Object leaks
13990             if (Ext.isIE) {
13991                 var t = {};
13992                 for (eid in EC) {
13993                     if (!EC.hasOwnProperty(eid)) {
13994                         continue;
13995                     }
13996                     t[eid] = EC[eid];
13997                 }
13998                 EC = Ext.cache = t;
13999             }
14000         }
14001     }
14002     El.collectorThreadId = setInterval(garbageCollect, 30000);
14003
14004     var flyFn = function() {};
14005     flyFn.prototype = El.prototype;
14006
14007     // dom is optional
14008     El.Flyweight = function(dom) {
14009         this.dom = dom;
14010     };
14011
14012     El.Flyweight.prototype = new flyFn();
14013     El.Flyweight.prototype.isFlyweight = true;
14014     El._flyweights = {};
14015
14016     /**
14017      * Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference
14018      * to this element - the dom node can be overwritten by other code. {@link Ext#fly} is alias for
14019      * {@link Ext.Element#fly}.
14020      *
14021      * Use this to make one-time references to DOM elements which are not going to be accessed again either by
14022      * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link
14023      * Ext#get Ext.get} will be more appropriate to take advantage of the caching provided by the Ext.Element
14024      * class.
14025      *
14026      * @param {String/HTMLElement} el The dom node or id
14027      * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts (e.g.
14028      * internally Ext uses "_global")
14029      * @return {Ext.Element} The shared Element object (or null if no matching element was found)
14030      * @static
14031      */
14032     El.fly = function(el, named) {
14033         var ret = null;
14034         named = named || '_global';
14035         el = Ext.getDom(el);
14036         if (el) {
14037             (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
14038             ret = El._flyweights[named];
14039         }
14040         return ret;
14041     };
14042
14043     /**
14044      * @member Ext
14045      * @method get
14046      * @alias Ext.Element#get
14047      */
14048     Ext.get = El.get;
14049
14050     /**
14051      * @member Ext
14052      * @method fly
14053      * @alias Ext.Element#fly
14054      */
14055     Ext.fly = El.fly;
14056
14057     // speedy lookup for elements never to box adjust
14058     var noBoxAdjust = Ext.isStrict ? {
14059         select: 1
14060     }: {
14061         input: 1,
14062         select: 1,
14063         textarea: 1
14064     };
14065     if (Ext.isIE || Ext.isGecko) {
14066         noBoxAdjust['button'] = 1;
14067     }
14068 })();
14069
14070 /**
14071  * @class Ext.Element
14072  */
14073 Ext.Element.addMethods({
14074     /**
14075      * 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)
14076      * @param {String} selector The simple selector to test
14077      * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
14078      * The max depth to search as a number or element (defaults to 50 || document.body)
14079      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
14080      * @return {HTMLElement} The matching DOM node (or null if no match was found)
14081      */
14082     findParent : function(simpleSelector, maxDepth, returnEl) {
14083         var p = this.dom,
14084             b = document.body,
14085             depth = 0,
14086             stopEl;
14087
14088         maxDepth = maxDepth || 50;
14089         if (isNaN(maxDepth)) {
14090             stopEl = Ext.getDom(maxDepth);
14091             maxDepth = Number.MAX_VALUE;
14092         }
14093         while (p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl) {
14094             if (Ext.DomQuery.is(p, simpleSelector)) {
14095                 return returnEl ? Ext.get(p) : p;
14096             }
14097             depth++;
14098             p = p.parentNode;
14099         }
14100         return null;
14101     },
14102
14103     /**
14104      * Looks at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
14105      * @param {String} selector The simple selector to test
14106      * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
14107      * The max depth to search as a number or element (defaults to 10 || document.body)
14108      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
14109      * @return {HTMLElement} The matching DOM node (or null if no match was found)
14110      */
14111     findParentNode : function(simpleSelector, maxDepth, returnEl) {
14112         var p = Ext.fly(this.dom.parentNode, '_internal');
14113         return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
14114     },
14115
14116     /**
14117      * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. div.some-class or span:first-child).
14118      * This is a shortcut for findParentNode() that always returns an Ext.Element.
14119      * @param {String} selector The simple selector to test
14120      * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
14121      * The max depth to search as a number or element (defaults to 10 || document.body)
14122      * @return {Ext.Element} The matching DOM node (or null if no match was found)
14123      */
14124     up : function(simpleSelector, maxDepth) {
14125         return this.findParentNode(simpleSelector, maxDepth, true);
14126     },
14127
14128     /**
14129      * Creates a {@link Ext.CompositeElement} for child nodes based on the passed CSS selector (the selector should not contain an id).
14130      * @param {String} selector The CSS selector
14131      * @return {Ext.CompositeElement/Ext.CompositeElement} The composite element
14132      */
14133     select : function(selector) {
14134         return Ext.Element.select(selector, false,  this.dom);
14135     },
14136
14137     /**
14138      * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
14139      * @param {String} selector The CSS selector
14140      * @return {HTMLElement[]} An array of the matched nodes
14141      */
14142     query : function(selector) {
14143         return Ext.DomQuery.select(selector, this.dom);
14144     },
14145
14146     /**
14147      * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
14148      * @param {String} selector The CSS selector
14149      * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
14150      * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
14151      */
14152     down : function(selector, returnDom) {
14153         var n = Ext.DomQuery.selectNode(selector, this.dom);
14154         return returnDom ? n : Ext.get(n);
14155     },
14156
14157     /**
14158      * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
14159      * @param {String} selector The CSS selector
14160      * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
14161      * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
14162      */
14163     child : function(selector, returnDom) {
14164         var node,
14165             me = this,
14166             id;
14167         id = Ext.get(me).id;
14168         // Escape . or :
14169         id = id.replace(/[\.:]/g, "\\$0");
14170         node = Ext.DomQuery.selectNode('#' + id + " > " + selector, me.dom);
14171         return returnDom ? node : Ext.get(node);
14172     },
14173
14174      /**
14175      * Gets the parent node for this element, optionally chaining up trying to match a selector
14176      * @param {String} selector (optional) Find a parent node that matches the passed simple selector
14177      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
14178      * @return {Ext.Element/HTMLElement} The parent node or null
14179      */
14180     parent : function(selector, returnDom) {
14181         return this.matchNode('parentNode', 'parentNode', selector, returnDom);
14182     },
14183
14184      /**
14185      * Gets the next sibling, 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 next sibling or null
14189      */
14190     next : function(selector, returnDom) {
14191         return this.matchNode('nextSibling', 'nextSibling', selector, returnDom);
14192     },
14193
14194     /**
14195      * Gets the previous sibling, 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 previous sibling or null
14199      */
14200     prev : function(selector, returnDom) {
14201         return this.matchNode('previousSibling', 'previousSibling', selector, returnDom);
14202     },
14203
14204
14205     /**
14206      * Gets the first child, skipping text nodes
14207      * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
14208      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
14209      * @return {Ext.Element/HTMLElement} The first child or null
14210      */
14211     first : function(selector, returnDom) {
14212         return this.matchNode('nextSibling', 'firstChild', selector, returnDom);
14213     },
14214
14215     /**
14216      * Gets the last child, skipping text nodes
14217      * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
14218      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
14219      * @return {Ext.Element/HTMLElement} The last child or null
14220      */
14221     last : function(selector, returnDom) {
14222         return this.matchNode('previousSibling', 'lastChild', selector, returnDom);
14223     },
14224
14225     matchNode : function(dir, start, selector, returnDom) {
14226         if (!this.dom) {
14227             return null;
14228         }
14229
14230         var n = this.dom[start];
14231         while (n) {
14232             if (n.nodeType == 1 && (!selector || Ext.DomQuery.is(n, selector))) {
14233                 return !returnDom ? Ext.get(n) : n;
14234             }
14235             n = n[dir];
14236         }
14237         return null;
14238     }
14239 });
14240
14241 /**
14242  * @class Ext.Element
14243  */
14244 Ext.Element.addMethods({
14245     /**
14246      * Appends the passed element(s) to this element
14247      * @param {String/HTMLElement/Ext.Element} el
14248      * The id of the node, a DOM Node or an existing Element.
14249      * @return {Ext.Element} this
14250      */
14251     appendChild : function(el) {
14252         return Ext.get(el).appendTo(this);
14253     },
14254
14255     /**
14256      * Appends this element to the passed element
14257      * @param {String/HTMLElement/Ext.Element} el The new parent element.
14258      * The id of the node, a DOM Node or an existing Element.
14259      * @return {Ext.Element} this
14260      */
14261     appendTo : function(el) {
14262         Ext.getDom(el).appendChild(this.dom);
14263         return this;
14264     },
14265
14266     /**
14267      * Inserts this element before the passed element in the DOM
14268      * @param {String/HTMLElement/Ext.Element} el The element before which this element will be inserted.
14269      * The id of the node, a DOM Node or an existing Element.
14270      * @return {Ext.Element} this
14271      */
14272     insertBefore : function(el) {
14273         el = Ext.getDom(el);
14274         el.parentNode.insertBefore(this.dom, el);
14275         return this;
14276     },
14277
14278     /**
14279      * Inserts this element after the passed element in the DOM
14280      * @param {String/HTMLElement/Ext.Element} el The element to insert after.
14281      * The id of the node, a DOM Node or an existing Element.
14282      * @return {Ext.Element} this
14283      */
14284     insertAfter : function(el) {
14285         el = Ext.getDom(el);
14286         el.parentNode.insertBefore(this.dom, el.nextSibling);
14287         return this;
14288     },
14289
14290     /**
14291      * Inserts (or creates) an element (or DomHelper config) as the first child of this element
14292      * @param {String/HTMLElement/Ext.Element/Object} el The id or element to insert or a DomHelper config
14293      * to create and insert
14294      * @return {Ext.Element} The new child
14295      */
14296     insertFirst : function(el, returnDom) {
14297         el = el || {};
14298         if (el.nodeType || el.dom || typeof el == 'string') { // element
14299             el = Ext.getDom(el);
14300             this.dom.insertBefore(el, this.dom.firstChild);
14301             return !returnDom ? Ext.get(el) : el;
14302         }
14303         else { // dh config
14304             return this.createChild(el, this.dom.firstChild, returnDom);
14305         }
14306     },
14307
14308     /**
14309      * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
14310      * @param {String/HTMLElement/Ext.Element/Object/Array} el The id, element to insert or a DomHelper config
14311      * to create and insert *or* an array of any of those.
14312      * @param {String} where (optional) 'before' or 'after' defaults to before
14313      * @param {Boolean} returnDom (optional) True to return the .;ll;l,raw DOM element instead of Ext.Element
14314      * @return {Ext.Element} The inserted Element. If an array is passed, the last inserted element is returned.
14315      */
14316     insertSibling: function(el, where, returnDom){
14317         var me = this, rt,
14318         isAfter = (where || 'before').toLowerCase() == 'after',
14319         insertEl;
14320
14321         if(Ext.isArray(el)){
14322             insertEl = me;
14323             Ext.each(el, function(e) {
14324                 rt = Ext.fly(insertEl, '_internal').insertSibling(e, where, returnDom);
14325                 if(isAfter){
14326                     insertEl = rt;
14327                 }
14328             });
14329             return rt;
14330         }
14331
14332         el = el || {};
14333
14334         if(el.nodeType || el.dom){
14335             rt = me.dom.parentNode.insertBefore(Ext.getDom(el), isAfter ? me.dom.nextSibling : me.dom);
14336             if (!returnDom) {
14337                 rt = Ext.get(rt);
14338             }
14339         }else{
14340             if (isAfter && !me.dom.nextSibling) {
14341                 rt = Ext.DomHelper.append(me.dom.parentNode, el, !returnDom);
14342             } else {
14343                 rt = Ext.DomHelper[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
14344             }
14345         }
14346         return rt;
14347     },
14348
14349     /**
14350      * Replaces the passed element with this element
14351      * @param {String/HTMLElement/Ext.Element} el The element to replace.
14352      * The id of the node, a DOM Node or an existing Element.
14353      * @return {Ext.Element} this
14354      */
14355     replace : function(el) {
14356         el = Ext.get(el);
14357         this.insertBefore(el);
14358         el.remove();
14359         return this;
14360     },
14361     
14362     /**
14363      * Replaces this element with the passed element
14364      * @param {String/HTMLElement/Ext.Element/Object} el The new element (id of the node, a DOM Node
14365      * or an existing Element) or a DomHelper config of an element to create
14366      * @return {Ext.Element} this
14367      */
14368     replaceWith: function(el){
14369         var me = this;
14370             
14371         if(el.nodeType || el.dom || typeof el == 'string'){
14372             el = Ext.get(el);
14373             me.dom.parentNode.insertBefore(el, me.dom);
14374         }else{
14375             el = Ext.DomHelper.insertBefore(me.dom, el);
14376         }
14377         
14378         delete Ext.cache[me.id];
14379         Ext.removeNode(me.dom);      
14380         me.id = Ext.id(me.dom = el);
14381         Ext.Element.addToCache(me.isFlyweight ? new Ext.Element(me.dom) : me);     
14382         return me;
14383     },
14384     
14385     /**
14386      * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
14387      * @param {Object} config DomHelper element config object.  If no tag is specified (e.g., {tag:'input'}) then a div will be
14388      * automatically generated with the specified attributes.
14389      * @param {HTMLElement} insertBefore (optional) a child element of this element
14390      * @param {Boolean} returnDom (optional) true to return the dom node instead of creating an Element
14391      * @return {Ext.Element} The new child element
14392      */
14393     createChild : function(config, insertBefore, returnDom) {
14394         config = config || {tag:'div'};
14395         if (insertBefore) {
14396             return Ext.DomHelper.insertBefore(insertBefore, config, returnDom !== true);
14397         }
14398         else {
14399             return Ext.DomHelper[!this.dom.firstChild ? 'insertFirst' : 'append'](this.dom, config,  returnDom !== true);
14400         }
14401     },
14402
14403     /**
14404      * Creates and wraps this element with another element
14405      * @param {Object} config (optional) DomHelper element config object for the wrapper element or null for an empty div
14406      * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.Element
14407      * @return {HTMLElement/Ext.Element} The newly created wrapper element
14408      */
14409     wrap : function(config, returnDom) {
14410         var newEl = Ext.DomHelper.insertBefore(this.dom, config || {tag: "div"}, !returnDom),
14411             d = newEl.dom || newEl;
14412
14413         d.appendChild(this.dom);
14414         return newEl;
14415     },
14416
14417     /**
14418      * Inserts an html fragment into this element
14419      * @param {String} where Where to insert the html in relation to this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
14420      * See {@link Ext.DomHelper#insertHtml} for details.
14421      * @param {String} html The HTML fragment
14422      * @param {Boolean} returnEl (optional) True to return an Ext.Element (defaults to false)
14423      * @return {HTMLElement/Ext.Element} The inserted node (or nearest related if more than 1 inserted)
14424      */
14425     insertHtml : function(where, html, returnEl) {
14426         var el = Ext.DomHelper.insertHtml(where, this.dom, html);
14427         return returnEl ? Ext.get(el) : el;
14428     }
14429 });
14430
14431 /**
14432  * @class Ext.Element
14433  */
14434 (function(){
14435     // local style camelizing for speed
14436     var ELEMENT = Ext.Element,
14437         supports = Ext.supports,
14438         view = document.defaultView,
14439         opacityRe = /alpha\(opacity=(.*)\)/i,
14440         trimRe = /^\s+|\s+$/g,
14441         spacesRe = /\s+/,
14442         wordsRe = /\w/g,
14443         adjustDirect2DTableRe = /table-row|table-.*-group/,
14444         INTERNAL = '_internal',
14445         PADDING = 'padding',
14446         MARGIN = 'margin',
14447         BORDER = 'border',
14448         LEFT = '-left',
14449         RIGHT = '-right',
14450         TOP = '-top',
14451         BOTTOM = '-bottom',
14452         WIDTH = '-width',
14453         MATH = Math,
14454         HIDDEN = 'hidden',
14455         ISCLIPPED = 'isClipped',
14456         OVERFLOW = 'overflow',
14457         OVERFLOWX = 'overflow-x',
14458         OVERFLOWY = 'overflow-y',
14459         ORIGINALCLIP = 'originalClip',
14460         // special markup used throughout Ext when box wrapping elements
14461         borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
14462         paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
14463         margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
14464         data = ELEMENT.data;
14465
14466     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>';
14467
14468     // These property values are read from the parentNode if they cannot be read
14469     // from the child:
14470     ELEMENT.inheritedProps = {
14471         fontSize: 1,
14472         fontStyle: 1,
14473         opacity: 1
14474     };
14475
14476     Ext.override(ELEMENT, {
14477
14478         /**
14479          * TODO: Look at this
14480          */
14481         // private  ==> used by Fx
14482         adjustWidth : function(width) {
14483             var me = this,
14484                 isNum = (typeof width == 'number');
14485
14486             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
14487                width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
14488             }
14489             return (isNum && width < 0) ? 0 : width;
14490         },
14491
14492         // private   ==> used by Fx
14493         adjustHeight : function(height) {
14494             var me = this,
14495                 isNum = (typeof height == "number");
14496
14497             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
14498                height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
14499             }
14500             return (isNum && height < 0) ? 0 : height;
14501         },
14502
14503
14504         /**
14505          * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
14506          * @param {String/String[]} className The CSS classes to add separated by space, or an array of classes
14507          * @return {Ext.Element} this
14508          */
14509         addCls : function(className){
14510             var me = this,
14511                 cls = [],
14512                 space = ((me.dom.className.replace(trimRe, '') == '') ? "" : " "),
14513                 i, len, v;
14514             if (className === undefined) {
14515                 return me;
14516             }
14517             // Separate case is for speed
14518             if (Object.prototype.toString.call(className) !== '[object Array]') {
14519                 if (typeof className === 'string') {
14520                     className = className.replace(trimRe, '').split(spacesRe);
14521                     if (className.length === 1) {
14522                         className = className[0];
14523                         if (!me.hasCls(className)) {
14524                             me.dom.className += space + className;
14525                         }
14526                     } else {
14527                         this.addCls(className);
14528                     }
14529                 }
14530             } else {
14531                 for (i = 0, len = className.length; i < len; i++) {
14532                     v = className[i];
14533                     if (typeof v == 'string' && (' ' + me.dom.className + ' ').indexOf(' ' + v + ' ') == -1) {
14534                         cls.push(v);
14535                     }
14536                 }
14537                 if (cls.length) {
14538                     me.dom.className += space + cls.join(" ");
14539                 }
14540             }
14541             return me;
14542         },
14543
14544         /**
14545          * Removes one or more CSS classes from the element.
14546          * @param {String/String[]} className The CSS classes to remove separated by space, or an array of classes
14547          * @return {Ext.Element} this
14548          */
14549         removeCls : function(className){
14550             var me = this,
14551                 i, idx, len, cls, elClasses;
14552             if (className === undefined) {
14553                 return me;
14554             }
14555             if (Object.prototype.toString.call(className) !== '[object Array]') {
14556                 className = className.replace(trimRe, '').split(spacesRe);
14557             }
14558             if (me.dom && me.dom.className) {
14559                 elClasses = me.dom.className.replace(trimRe, '').split(spacesRe);
14560                 for (i = 0, len = className.length; i < len; i++) {
14561                     cls = className[i];
14562                     if (typeof cls == 'string') {
14563                         cls = cls.replace(trimRe, '');
14564                         idx = Ext.Array.indexOf(elClasses, cls);
14565                         if (idx != -1) {
14566                             Ext.Array.erase(elClasses, idx, 1);
14567                         }
14568                     }
14569                 }
14570                 me.dom.className = elClasses.join(" ");
14571             }
14572             return me;
14573         },
14574
14575         /**
14576          * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
14577          * @param {String/String[]} className The CSS class to add, or an array of classes
14578          * @return {Ext.Element} this
14579          */
14580         radioCls : function(className){
14581             var cn = this.dom.parentNode.childNodes,
14582                 v, i, len;
14583             className = Ext.isArray(className) ? className : [className];
14584             for (i = 0, len = cn.length; i < len; i++) {
14585                 v = cn[i];
14586                 if (v && v.nodeType == 1) {
14587                     Ext.fly(v, '_internal').removeCls(className);
14588                 }
14589             }
14590             return this.addCls(className);
14591         },
14592
14593         /**
14594          * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
14595          * @param {String} className The CSS class to toggle
14596          * @return {Ext.Element} this
14597          * @method
14598          */
14599         toggleCls : Ext.supports.ClassList ?
14600             function(className) {
14601                 this.dom.classList.toggle(Ext.String.trim(className));
14602                 return this;
14603             } :
14604             function(className) {
14605                 return this.hasCls(className) ? this.removeCls(className) : this.addCls(className);
14606             },
14607
14608         /**
14609          * Checks if the specified CSS class exists on this element's DOM node.
14610          * @param {String} className The CSS class to check for
14611          * @return {Boolean} True if the class exists, else false
14612          * @method
14613          */
14614         hasCls : Ext.supports.ClassList ?
14615             function(className) {
14616                 if (!className) {
14617                     return false;
14618                 }
14619                 className = className.split(spacesRe);
14620                 var ln = className.length,
14621                     i = 0;
14622                 for (; i < ln; i++) {
14623                     if (className[i] && this.dom.classList.contains(className[i])) {
14624                         return true;
14625                     }
14626                 }
14627                 return false;
14628             } :
14629             function(className){
14630                 return className && (' ' + this.dom.className + ' ').indexOf(' ' + className + ' ') != -1;
14631             },
14632
14633         /**
14634          * Replaces a CSS class on the element with another.  If the old name does not exist, the new name will simply be added.
14635          * @param {String} oldClassName The CSS class to replace
14636          * @param {String} newClassName The replacement CSS class
14637          * @return {Ext.Element} this
14638          */
14639         replaceCls : function(oldClassName, newClassName){
14640             return this.removeCls(oldClassName).addCls(newClassName);
14641         },
14642
14643         isStyle : function(style, val) {
14644             return this.getStyle(style) == val;
14645         },
14646
14647         /**
14648          * Normalizes currentStyle and computedStyle.
14649          * @param {String} property The style property whose value is returned.
14650          * @return {String} The current value of the style property for this element.
14651          * @method
14652          */
14653         getStyle : function() {
14654             return view && view.getComputedStyle ?
14655                 function(prop){
14656                     var el = this.dom,
14657                         v, cs, out, display, cleaner;
14658
14659                     if(el == document){
14660                         return null;
14661                     }
14662                     prop = ELEMENT.normalize(prop);
14663                     out = (v = el.style[prop]) ? v :
14664                            (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
14665
14666                     // Ignore cases when the margin is correctly reported as 0, the bug only shows
14667                     // numbers larger.
14668                     if(prop == 'marginRight' && out != '0px' && !supports.RightMargin){
14669                         cleaner = ELEMENT.getRightMarginFixCleaner(el);
14670                         display = this.getStyle('display');
14671                         el.style.display = 'inline-block';
14672                         out = view.getComputedStyle(el, '').marginRight;
14673                         el.style.display = display;
14674                         cleaner();
14675                     }
14676
14677                     if(prop == 'backgroundColor' && out == 'rgba(0, 0, 0, 0)' && !supports.TransparentColor){
14678                         out = 'transparent';
14679                     }
14680                     return out;
14681                 } :
14682                 function (prop) {
14683                     var el = this.dom,
14684                         m, cs;
14685
14686                     if (el == document) {
14687                         return null;
14688                     }
14689                     prop = ELEMENT.normalize(prop);
14690
14691                     do {
14692                         if (prop == 'opacity') {
14693                             if (el.style.filter.match) {
14694                                 m = el.style.filter.match(opacityRe);
14695                                 if(m){
14696                                     var fv = parseFloat(m[1]);
14697                                     if(!isNaN(fv)){
14698                                         return fv ? fv / 100 : 0;
14699                                     }
14700                                 }
14701                             }
14702                             return 1;
14703                         }
14704
14705                         // the try statement does have a cost, so we avoid it unless we are
14706                         // on IE6
14707                         if (!Ext.isIE6) {
14708                             return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
14709                         }
14710
14711                         try {
14712                             return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
14713                         } catch (e) {
14714                             // in some cases, IE6 will throw Invalid Argument for properties
14715                             // like fontSize (see in /examples/tabs/tabs.html).
14716                         }
14717
14718                         if (!ELEMENT.inheritedProps[prop]) {
14719                             break;
14720                         }
14721
14722                         el = el.parentNode;
14723                         // this is _not_ perfect, but we can only hope that the style we
14724                         // need is inherited from a parentNode. If not and since IE won't
14725                         // give us the info we need, we are never going to be 100% right.
14726                     } while (el);
14727
14728                     Ext.log({
14729                         level: 'warn',
14730                         msg: 'Failed to get ' + this.dom.id + '.currentStyle.' + prop
14731                     });
14732                     return null;
14733                 }
14734         }(),
14735
14736         /**
14737          * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
14738          * are convert to standard 6 digit hex color.
14739          * @param {String} attr The css attribute
14740          * @param {String} defaultValue The default value to use when a valid color isn't found
14741          * @param {String} prefix (optional) defaults to #. Use an empty string when working with
14742          * color anims.
14743          */
14744         getColor : function(attr, defaultValue, prefix){
14745             var v = this.getStyle(attr),
14746                 color = prefix || prefix === '' ? prefix : '#',
14747                 h;
14748
14749             if(!v || (/transparent|inherit/.test(v))) {
14750                 return defaultValue;
14751             }
14752             if(/^r/.test(v)){
14753                 Ext.each(v.slice(4, v.length -1).split(','), function(s){
14754                     h = parseInt(s, 10);
14755                     color += (h < 16 ? '0' : '') + h.toString(16);
14756                 });
14757             }else{
14758                 v = v.replace('#', '');
14759                 color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
14760             }
14761             return(color.length > 5 ? color.toLowerCase() : defaultValue);
14762         },
14763
14764         /**
14765          * Wrapper for setting style properties, also takes single object parameter of multiple styles.
14766          * @param {String/Object} property The style property to be set, or an object of multiple styles.
14767          * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
14768          * @return {Ext.Element} this
14769          */
14770         setStyle : function(prop, value){
14771             var me = this,
14772                 tmp, style;
14773
14774             if (!me.dom) {
14775                 return me;
14776             }
14777             if (typeof prop === 'string') {
14778                 tmp = {};
14779                 tmp[prop] = value;
14780                 prop = tmp;
14781             }
14782             for (style in prop) {
14783                 if (prop.hasOwnProperty(style)) {
14784                     value = Ext.value(prop[style], '');
14785                     if (style == 'opacity') {
14786                         me.setOpacity(value);
14787                     }
14788                     else {
14789                         me.dom.style[ELEMENT.normalize(style)] = value;
14790                     }
14791                 }
14792             }
14793             return me;
14794         },
14795
14796         /**
14797          * Set the opacity of the element
14798          * @param {Number} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
14799          * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
14800          * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
14801          * @return {Ext.Element} this
14802          */
14803         setOpacity: function(opacity, animate) {
14804             var me = this,
14805                 dom = me.dom,
14806                 val,
14807                 style;
14808
14809             if (!me.dom) {
14810                 return me;
14811             }
14812
14813             style = me.dom.style;
14814
14815             if (!animate || !me.anim) {
14816                 if (!Ext.supports.Opacity) {
14817                     opacity = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')': '';
14818                     val = style.filter.replace(opacityRe, '').replace(trimRe, '');
14819
14820                     style.zoom = 1;
14821                     style.filter = val + (val.length > 0 ? ' ': '') + opacity;
14822                 }
14823                 else {
14824                     style.opacity = opacity;
14825                 }
14826             }
14827             else {
14828                 if (!Ext.isObject(animate)) {
14829                     animate = {
14830                         duration: 350,
14831                         easing: 'ease-in'
14832                     };
14833                 }
14834                 me.animate(Ext.applyIf({
14835                     to: {
14836                         opacity: opacity
14837                     }
14838                 },
14839                 animate));
14840             }
14841             return me;
14842         },
14843
14844
14845         /**
14846          * Clears any opacity settings from this element. Required in some cases for IE.
14847          * @return {Ext.Element} this
14848          */
14849         clearOpacity : function(){
14850             var style = this.dom.style;
14851             if(!Ext.supports.Opacity){
14852                 if(!Ext.isEmpty(style.filter)){
14853                     style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
14854                 }
14855             }else{
14856                 style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
14857             }
14858             return this;
14859         },
14860
14861         /**
14862          * @private
14863          * Returns 1 if the browser returns the subpixel dimension rounded to the lowest pixel.
14864          * @return {Number} 0 or 1
14865          */
14866         adjustDirect2DDimension: function(dimension) {
14867             var me = this,
14868                 dom = me.dom,
14869                 display = me.getStyle('display'),
14870                 inlineDisplay = dom.style['display'],
14871                 inlinePosition = dom.style['position'],
14872                 originIndex = dimension === 'width' ? 0 : 1,
14873                 floating;
14874
14875             if (display === 'inline') {
14876                 dom.style['display'] = 'inline-block';
14877             }
14878
14879             dom.style['position'] = display.match(adjustDirect2DTableRe) ? 'absolute' : 'static';
14880
14881             // floating will contain digits that appears after the decimal point
14882             // if height or width are set to auto we fallback to msTransformOrigin calculation
14883             floating = (parseFloat(me.getStyle(dimension)) || parseFloat(dom.currentStyle.msTransformOrigin.split(' ')[originIndex]) * 2) % 1;
14884
14885             dom.style['position'] = inlinePosition;
14886
14887             if (display === 'inline') {
14888                 dom.style['display'] = inlineDisplay;
14889             }
14890
14891             return floating;
14892         },
14893
14894         /**
14895          * Returns the offset height of the element
14896          * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
14897          * @return {Number} The element's height
14898          */
14899         getHeight: function(contentHeight, preciseHeight) {
14900             var me = this,
14901                 dom = me.dom,
14902                 hidden = Ext.isIE && me.isStyle('display', 'none'),
14903                 height, overflow, style, floating;
14904
14905             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
14906             // We will put the overflow back to it's original value when we are done measuring.
14907             if (Ext.isIEQuirks) {
14908                 style = dom.style;
14909                 overflow = style.overflow;
14910                 me.setStyle({ overflow: 'hidden'});
14911             }
14912
14913             height = dom.offsetHeight;
14914
14915             height = MATH.max(height, hidden ? 0 : dom.clientHeight) || 0;
14916
14917             // IE9 Direct2D dimension rounding bug
14918             if (!hidden && Ext.supports.Direct2DBug) {
14919                 floating = me.adjustDirect2DDimension('height');
14920                 if (preciseHeight) {
14921                     height += floating;
14922                 }
14923                 else if (floating > 0 && floating < 0.5) {
14924                     height++;
14925                 }
14926             }
14927
14928             if (contentHeight) {
14929                 height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
14930             }
14931
14932             if (Ext.isIEQuirks) {
14933                 me.setStyle({ overflow: overflow});
14934             }
14935
14936             if (height < 0) {
14937                 height = 0;
14938             }
14939             return height;
14940         },
14941
14942         /**
14943          * Returns the offset width of the element
14944          * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
14945          * @return {Number} The element's width
14946          */
14947         getWidth: function(contentWidth, preciseWidth) {
14948             var me = this,
14949                 dom = me.dom,
14950                 hidden = Ext.isIE && me.isStyle('display', 'none'),
14951                 rect, width, overflow, style, floating, parentPosition;
14952
14953             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
14954             // We will put the overflow back to it's original value when we are done measuring.
14955             if (Ext.isIEQuirks) {
14956                 style = dom.style;
14957                 overflow = style.overflow;
14958                 me.setStyle({overflow: 'hidden'});
14959             }
14960
14961             // Fix Opera 10.5x width calculation issues
14962             if (Ext.isOpera10_5) {
14963                 if (dom.parentNode.currentStyle.position === 'relative') {
14964                     parentPosition = dom.parentNode.style.position;
14965                     dom.parentNode.style.position = 'static';
14966                     width = dom.offsetWidth;
14967                     dom.parentNode.style.position = parentPosition;
14968                 }
14969                 width = Math.max(width || 0, dom.offsetWidth);
14970
14971             // Gecko will in some cases report an offsetWidth that is actually less than the width of the
14972             // text contents, because it measures fonts with sub-pixel precision but rounds the calculated
14973             // value down. Using getBoundingClientRect instead of offsetWidth allows us to get the precise
14974             // subpixel measurements so we can force them to always be rounded up. See
14975             // https://bugzilla.mozilla.org/show_bug.cgi?id=458617
14976             } else if (Ext.supports.BoundingClientRect) {
14977                 rect = dom.getBoundingClientRect();
14978                 width = rect.right - rect.left;
14979                 width = preciseWidth ? width : Math.ceil(width);
14980             } else {
14981                 width = dom.offsetWidth;
14982             }
14983
14984             width = MATH.max(width, hidden ? 0 : dom.clientWidth) || 0;
14985
14986             // IE9 Direct2D dimension rounding bug
14987             if (!hidden && Ext.supports.Direct2DBug) {
14988                 floating = me.adjustDirect2DDimension('width');
14989                 if (preciseWidth) {
14990                     width += floating;
14991                 }
14992                 else if (floating > 0 && floating < 0.5) {
14993                     width++;
14994                 }
14995             }
14996
14997             if (contentWidth) {
14998                 width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
14999             }
15000
15001             if (Ext.isIEQuirks) {
15002                 me.setStyle({ overflow: overflow});
15003             }
15004
15005             if (width < 0) {
15006                 width = 0;
15007             }
15008             return width;
15009         },
15010
15011         /**
15012          * Set the width of this Element.
15013          * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
15014          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
15015          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
15016          * </ul></div>
15017          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15018          * @return {Ext.Element} this
15019          */
15020         setWidth : function(width, animate){
15021             var me = this;
15022             width = me.adjustWidth(width);
15023             if (!animate || !me.anim) {
15024                 me.dom.style.width = me.addUnits(width);
15025             }
15026             else {
15027                 if (!Ext.isObject(animate)) {
15028                     animate = {};
15029                 }
15030                 me.animate(Ext.applyIf({
15031                     to: {
15032                         width: width
15033                     }
15034                 }, animate));
15035             }
15036             return me;
15037         },
15038
15039         /**
15040          * Set the height of this Element.
15041          * <pre><code>
15042 // change the height to 200px and animate with default configuration
15043 Ext.fly('elementId').setHeight(200, true);
15044
15045 // change the height to 150px and animate with a custom configuration
15046 Ext.fly('elId').setHeight(150, {
15047     duration : .5, // animation will have a duration of .5 seconds
15048     // will change the content to "finished"
15049     callback: function(){ this.{@link #update}("finished"); }
15050 });
15051          * </code></pre>
15052          * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
15053          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)</li>
15054          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
15055          * </ul></div>
15056          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15057          * @return {Ext.Element} this
15058          */
15059          setHeight : function(height, animate){
15060             var me = this;
15061             height = me.adjustHeight(height);
15062             if (!animate || !me.anim) {
15063                 me.dom.style.height = me.addUnits(height);
15064             }
15065             else {
15066                 if (!Ext.isObject(animate)) {
15067                     animate = {};
15068                 }
15069                 me.animate(Ext.applyIf({
15070                     to: {
15071                         height: height
15072                     }
15073                 }, animate));
15074             }
15075             return me;
15076         },
15077
15078         /**
15079          * Gets the width of the border(s) for the specified side(s)
15080          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
15081          * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
15082          * @return {Number} The width of the sides passed added together
15083          */
15084         getBorderWidth : function(side){
15085             return this.addStyles(side, borders);
15086         },
15087
15088         /**
15089          * Gets the width of the padding(s) for the specified side(s)
15090          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
15091          * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
15092          * @return {Number} The padding of the sides passed added together
15093          */
15094         getPadding : function(side){
15095             return this.addStyles(side, paddings);
15096         },
15097
15098         /**
15099          *  Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
15100          * @return {Ext.Element} this
15101          */
15102         clip : function(){
15103             var me = this,
15104                 dom = me.dom;
15105
15106             if(!data(dom, ISCLIPPED)){
15107                 data(dom, ISCLIPPED, true);
15108                 data(dom, ORIGINALCLIP, {
15109                     o: me.getStyle(OVERFLOW),
15110                     x: me.getStyle(OVERFLOWX),
15111                     y: me.getStyle(OVERFLOWY)
15112                 });
15113                 me.setStyle(OVERFLOW, HIDDEN);
15114                 me.setStyle(OVERFLOWX, HIDDEN);
15115                 me.setStyle(OVERFLOWY, HIDDEN);
15116             }
15117             return me;
15118         },
15119
15120         /**
15121          *  Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
15122          * @return {Ext.Element} this
15123          */
15124         unclip : function(){
15125             var me = this,
15126                 dom = me.dom,
15127                 clip;
15128
15129             if(data(dom, ISCLIPPED)){
15130                 data(dom, ISCLIPPED, false);
15131                 clip = data(dom, ORIGINALCLIP);
15132                 if(clip.o){
15133                     me.setStyle(OVERFLOW, clip.o);
15134                 }
15135                 if(clip.x){
15136                     me.setStyle(OVERFLOWX, clip.x);
15137                 }
15138                 if(clip.y){
15139                     me.setStyle(OVERFLOWY, clip.y);
15140                 }
15141             }
15142             return me;
15143         },
15144
15145         // private
15146         addStyles : function(sides, styles){
15147             var totalSize = 0,
15148                 sidesArr = sides.match(wordsRe),
15149                 i = 0,
15150                 len = sidesArr.length,
15151                 side, size;
15152             for (; i < len; i++) {
15153                 side = sidesArr[i];
15154                 size = side && parseInt(this.getStyle(styles[side]), 10);
15155                 if (size) {
15156                     totalSize += MATH.abs(size);
15157                 }
15158             }
15159             return totalSize;
15160         },
15161
15162         margins : margins,
15163
15164         /**
15165          * More flexible version of {@link #setStyle} for setting style properties.
15166          * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
15167          * a function which returns such a specification.
15168          * @return {Ext.Element} this
15169          */
15170         applyStyles : function(style){
15171             Ext.DomHelper.applyStyles(this.dom, style);
15172             return this;
15173         },
15174
15175         /**
15176          * Returns an object with properties matching the styles requested.
15177          * For example, el.getStyles('color', 'font-size', 'width') might return
15178          * {'color': '#FFFFFF', 'font-size': '13px', 'width': '100px'}.
15179          * @param {String} style1 A style name
15180          * @param {String} style2 A style name
15181          * @param {String} etc.
15182          * @return {Object} The style object
15183          */
15184         getStyles : function(){
15185             var styles = {},
15186                 len = arguments.length,
15187                 i = 0, style;
15188
15189             for(; i < len; ++i) {
15190                 style = arguments[i];
15191                 styles[style] = this.getStyle(style);
15192             }
15193             return styles;
15194         },
15195
15196        /**
15197         * <p>Wraps the specified element with a special 9 element markup/CSS block that renders by default as
15198         * a gray container with a gradient background, rounded corners and a 4-way shadow.</p>
15199         * <p>This special markup is used throughout Ext when box wrapping elements ({@link Ext.button.Button},
15200         * {@link Ext.panel.Panel} when <tt>{@link Ext.panel.Panel#frame frame=true}</tt>, {@link Ext.window.Window}).  The markup
15201         * is of this form:</p>
15202         * <pre><code>
15203     Ext.Element.boxMarkup =
15204     &#39;&lt;div class="{0}-tl">&lt;div class="{0}-tr">&lt;div class="{0}-tc">&lt;/div>&lt;/div>&lt;/div>
15205      &lt;div class="{0}-ml">&lt;div class="{0}-mr">&lt;div class="{0}-mc">&lt;/div>&lt;/div>&lt;/div>
15206      &lt;div class="{0}-bl">&lt;div class="{0}-br">&lt;div class="{0}-bc">&lt;/div>&lt;/div>&lt;/div>&#39;;
15207         * </code></pre>
15208         * <p>Example usage:</p>
15209         * <pre><code>
15210     // Basic box wrap
15211     Ext.get("foo").boxWrap();
15212
15213     // You can also add a custom class and use CSS inheritance rules to customize the box look.
15214     // 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
15215     // for how to create a custom box wrap style.
15216     Ext.get("foo").boxWrap().addCls("x-box-blue");
15217         * </code></pre>
15218         * @param {String} class (optional) A base CSS class to apply to the containing wrapper element
15219         * (defaults to <tt>'x-box'</tt>). Note that there are a number of CSS rules that are dependent on
15220         * this name to make the overall effect work, so if you supply an alternate base class, make sure you
15221         * also supply all of the necessary rules.
15222         * @return {Ext.Element} The outermost wrapping element of the created box structure.
15223         */
15224         boxWrap : function(cls){
15225             cls = cls || Ext.baseCSSPrefix + 'box';
15226             var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "'>" + Ext.String.format(ELEMENT.boxMarkup, cls) + "</div>"));
15227             Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
15228             return el;
15229         },
15230
15231         /**
15232          * Set the size of this Element. If animation is true, both width and height will be animated concurrently.
15233          * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
15234          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
15235          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
15236          * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
15237          * </ul></div>
15238          * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
15239          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).</li>
15240          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
15241          * </ul></div>
15242          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15243          * @return {Ext.Element} this
15244          */
15245         setSize : function(width, height, animate){
15246             var me = this;
15247             if (Ext.isObject(width)) { // in case of object from getSize()
15248                 animate = height;
15249                 height = width.height;
15250                 width = width.width;
15251             }
15252             width = me.adjustWidth(width);
15253             height = me.adjustHeight(height);
15254             if(!animate || !me.anim){
15255                 // Must touch some property before setting style.width/height on non-quirk IE6,7, or the
15256                 // properties will not reflect the changes on the style immediately
15257                 if (!Ext.isIEQuirks && (Ext.isIE6 || Ext.isIE7)) {
15258                     me.dom.offsetTop;
15259                 }
15260                 me.dom.style.width = me.addUnits(width);
15261                 me.dom.style.height = me.addUnits(height);
15262             }
15263             else {
15264                 if (animate === true) {
15265                     animate = {};
15266                 }
15267                 me.animate(Ext.applyIf({
15268                     to: {
15269                         width: width,
15270                         height: height
15271                     }
15272                 }, animate));
15273             }
15274             return me;
15275         },
15276
15277         /**
15278          * Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders
15279          * when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements
15280          * if a height has not been set using CSS.
15281          * @return {Number}
15282          */
15283         getComputedHeight : function(){
15284             var me = this,
15285                 h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
15286             if(!h){
15287                 h = parseFloat(me.getStyle('height')) || 0;
15288                 if(!me.isBorderBox()){
15289                     h += me.getFrameWidth('tb');
15290                 }
15291             }
15292             return h;
15293         },
15294
15295         /**
15296          * Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders
15297          * when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements
15298          * if a width has not been set using CSS.
15299          * @return {Number}
15300          */
15301         getComputedWidth : function(){
15302             var me = this,
15303                 w = Math.max(me.dom.offsetWidth, me.dom.clientWidth);
15304
15305             if(!w){
15306                 w = parseFloat(me.getStyle('width')) || 0;
15307                 if(!me.isBorderBox()){
15308                     w += me.getFrameWidth('lr');
15309                 }
15310             }
15311             return w;
15312         },
15313
15314         /**
15315          * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
15316          for more information about the sides.
15317          * @param {String} sides
15318          * @return {Number}
15319          */
15320         getFrameWidth : function(sides, onlyContentBox){
15321             return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
15322         },
15323
15324         /**
15325          * Sets up event handlers to add and remove a css class when the mouse is over this element
15326          * @param {String} className
15327          * @return {Ext.Element} this
15328          */
15329         addClsOnOver : function(className){
15330             var dom = this.dom;
15331             this.hover(
15332                 function(){
15333                     Ext.fly(dom, INTERNAL).addCls(className);
15334                 },
15335                 function(){
15336                     Ext.fly(dom, INTERNAL).removeCls(className);
15337                 }
15338             );
15339             return this;
15340         },
15341
15342         /**
15343          * Sets up event handlers to add and remove a css class when this element has the focus
15344          * @param {String} className
15345          * @return {Ext.Element} this
15346          */
15347         addClsOnFocus : function(className){
15348             var me = this,
15349                 dom = me.dom;
15350             me.on("focus", function(){
15351                 Ext.fly(dom, INTERNAL).addCls(className);
15352             });
15353             me.on("blur", function(){
15354                 Ext.fly(dom, INTERNAL).removeCls(className);
15355             });
15356             return me;
15357         },
15358
15359         /**
15360          * 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)
15361          * @param {String} className
15362          * @return {Ext.Element} this
15363          */
15364         addClsOnClick : function(className){
15365             var dom = this.dom;
15366             this.on("mousedown", function(){
15367                 Ext.fly(dom, INTERNAL).addCls(className);
15368                 var d = Ext.getDoc(),
15369                     fn = function(){
15370                         Ext.fly(dom, INTERNAL).removeCls(className);
15371                         d.removeListener("mouseup", fn);
15372                     };
15373                 d.on("mouseup", fn);
15374             });
15375             return this;
15376         },
15377
15378         /**
15379          * <p>Returns the dimensions of the element available to lay content out in.<p>
15380          * <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
15381          * example:<pre><code>
15382         var vpSize = Ext.getBody().getViewSize();
15383
15384         // all Windows created afterwards will have a default value of 90% height and 95% width
15385         Ext.Window.override({
15386             width: vpSize.width * 0.9,
15387             height: vpSize.height * 0.95
15388         });
15389         // To handle window resizing you would have to hook onto onWindowResize.
15390         * </code></pre>
15391         *
15392         * getViewSize utilizes clientHeight/clientWidth which excludes sizing of scrollbars.
15393         * To obtain the size including scrollbars, use getStyleSize
15394         *
15395         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
15396         */
15397
15398         getViewSize : function(){
15399             var me = this,
15400                 dom = me.dom,
15401                 isDoc = (dom == Ext.getDoc().dom || dom == Ext.getBody().dom),
15402                 style, overflow, ret;
15403
15404             // If the body, use static methods
15405             if (isDoc) {
15406                 ret = {
15407                     width : ELEMENT.getViewWidth(),
15408                     height : ELEMENT.getViewHeight()
15409                 };
15410
15411             // Else use clientHeight/clientWidth
15412             }
15413             else {
15414                 // IE 6 & IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
15415                 // We will put the overflow back to it's original value when we are done measuring.
15416                 if (Ext.isIE6 || Ext.isIEQuirks) {
15417                     style = dom.style;
15418                     overflow = style.overflow;
15419                     me.setStyle({ overflow: 'hidden'});
15420                 }
15421                 ret = {
15422                     width : dom.clientWidth,
15423                     height : dom.clientHeight
15424                 };
15425                 if (Ext.isIE6 || Ext.isIEQuirks) {
15426                     me.setStyle({ overflow: overflow });
15427                 }
15428             }
15429             return ret;
15430         },
15431
15432         /**
15433         * <p>Returns the dimensions of the element available to lay content out in.<p>
15434         *
15435         * getStyleSize utilizes prefers style sizing if present, otherwise it chooses the larger of offsetHeight/clientHeight and offsetWidth/clientWidth.
15436         * To obtain the size excluding scrollbars, use getViewSize
15437         *
15438         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
15439         */
15440
15441         getStyleSize : function(){
15442             var me = this,
15443                 doc = document,
15444                 d = this.dom,
15445                 isDoc = (d == doc || d == doc.body),
15446                 s = d.style,
15447                 w, h;
15448
15449             // If the body, use static methods
15450             if (isDoc) {
15451                 return {
15452                     width : ELEMENT.getViewWidth(),
15453                     height : ELEMENT.getViewHeight()
15454                 };
15455             }
15456             // Use Styles if they are set
15457             if(s.width && s.width != 'auto'){
15458                 w = parseFloat(s.width);
15459                 if(me.isBorderBox()){
15460                    w -= me.getFrameWidth('lr');
15461                 }
15462             }
15463             // Use Styles if they are set
15464             if(s.height && s.height != 'auto'){
15465                 h = parseFloat(s.height);
15466                 if(me.isBorderBox()){
15467                    h -= me.getFrameWidth('tb');
15468                 }
15469             }
15470             // Use getWidth/getHeight if style not set.
15471             return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
15472         },
15473
15474         /**
15475          * Returns the size of the element.
15476          * @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
15477          * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
15478          */
15479         getSize : function(contentSize){
15480             return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
15481         },
15482
15483         /**
15484          * Forces the browser to repaint this element
15485          * @return {Ext.Element} this
15486          */
15487         repaint : function(){
15488             var dom = this.dom;
15489             this.addCls(Ext.baseCSSPrefix + 'repaint');
15490             setTimeout(function(){
15491                 Ext.fly(dom).removeCls(Ext.baseCSSPrefix + 'repaint');
15492             }, 1);
15493             return this;
15494         },
15495
15496         /**
15497          * Enable text selection for this element (normalized across browsers)
15498          * @return {Ext.Element} this
15499          */
15500         selectable : function() {
15501             var me = this;
15502             me.dom.unselectable = "off";
15503             // Prevent it from bubles up and enables it to be selectable
15504             me.on('selectstart', function (e) {
15505                 e.stopPropagation();
15506                 return true;
15507             });
15508             me.applyStyles("-moz-user-select: text; -khtml-user-select: text;");
15509             me.removeCls(Ext.baseCSSPrefix + 'unselectable');
15510             return me;
15511         },
15512
15513         /**
15514          * Disables text selection for this element (normalized across browsers)
15515          * @return {Ext.Element} this
15516          */
15517         unselectable : function(){
15518             var me = this;
15519             me.dom.unselectable = "on";
15520
15521             me.swallowEvent("selectstart", true);
15522             me.applyStyles("-moz-user-select:-moz-none;-khtml-user-select:none;");
15523             me.addCls(Ext.baseCSSPrefix + 'unselectable');
15524
15525             return me;
15526         },
15527
15528         /**
15529          * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
15530          * then it returns the calculated width of the sides (see getPadding)
15531          * @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
15532          * @return {Object/Number}
15533          */
15534         getMargin : function(side){
15535             var me = this,
15536                 hash = {t:"top", l:"left", r:"right", b: "bottom"},
15537                 o = {},
15538                 key;
15539
15540             if (!side) {
15541                 for (key in me.margins){
15542                     o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
15543                 }
15544                 return o;
15545             } else {
15546                 return me.addStyles.call(me, side, me.margins);
15547             }
15548         }
15549     });
15550 })();
15551 /**
15552  * @class Ext.Element
15553  */
15554 /**
15555  * Visibility mode constant for use with {@link #setVisibilityMode}. Use visibility to hide element
15556  * @static
15557  * @type Number
15558  */
15559 Ext.Element.VISIBILITY = 1;
15560 /**
15561  * Visibility mode constant for use with {@link #setVisibilityMode}. Use display to hide element
15562  * @static
15563  * @type Number
15564  */
15565 Ext.Element.DISPLAY = 2;
15566
15567 /**
15568  * Visibility mode constant for use with {@link #setVisibilityMode}. Use offsets (x and y positioning offscreen)
15569  * to hide element.
15570  * @static
15571  * @type Number
15572  */
15573 Ext.Element.OFFSETS = 3;
15574
15575
15576 Ext.Element.ASCLASS = 4;
15577
15578 /**
15579  * Defaults to 'x-hide-nosize'
15580  * @static
15581  * @type String
15582  */
15583 Ext.Element.visibilityCls = Ext.baseCSSPrefix + 'hide-nosize';
15584
15585 Ext.Element.addMethods(function(){
15586     var El = Ext.Element,
15587         OPACITY = "opacity",
15588         VISIBILITY = "visibility",
15589         DISPLAY = "display",
15590         HIDDEN = "hidden",
15591         OFFSETS = "offsets",
15592         ASCLASS = "asclass",
15593         NONE = "none",
15594         NOSIZE = 'nosize',
15595         ORIGINALDISPLAY = 'originalDisplay',
15596         VISMODE = 'visibilityMode',
15597         ISVISIBLE = 'isVisible',
15598         data = El.data,
15599         getDisplay = function(dom){
15600             var d = data(dom, ORIGINALDISPLAY);
15601             if(d === undefined){
15602                 data(dom, ORIGINALDISPLAY, d = '');
15603             }
15604             return d;
15605         },
15606         getVisMode = function(dom){
15607             var m = data(dom, VISMODE);
15608             if(m === undefined){
15609                 data(dom, VISMODE, m = 1);
15610             }
15611             return m;
15612         };
15613
15614     return {
15615         /**
15616          * @property {String} originalDisplay
15617          * The element's default display mode
15618          */
15619         originalDisplay : "",
15620         visibilityMode : 1,
15621
15622         /**
15623          * Sets the element's visibility mode. When setVisible() is called it
15624          * will use this to determine whether to set the visibility or the display property.
15625          * @param {Number} visMode Ext.Element.VISIBILITY or Ext.Element.DISPLAY
15626          * @return {Ext.Element} this
15627          */
15628         setVisibilityMode : function(visMode){
15629             data(this.dom, VISMODE, visMode);
15630             return this;
15631         },
15632
15633         /**
15634          * Checks whether the element is currently visible using both visibility and display properties.
15635          * @return {Boolean} True if the element is currently visible, else false
15636          */
15637         isVisible : function() {
15638             var me = this,
15639                 dom = me.dom,
15640                 visible = data(dom, ISVISIBLE);
15641
15642             if(typeof visible == 'boolean'){ //return the cached value if registered
15643                 return visible;
15644             }
15645             //Determine the current state based on display states
15646             visible = !me.isStyle(VISIBILITY, HIDDEN) &&
15647                       !me.isStyle(DISPLAY, NONE) &&
15648                       !((getVisMode(dom) == El.ASCLASS) && me.hasCls(me.visibilityCls || El.visibilityCls));
15649
15650             data(dom, ISVISIBLE, visible);
15651             return visible;
15652         },
15653
15654         /**
15655          * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
15656          * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
15657          * @param {Boolean} visible Whether the element is visible
15658          * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
15659          * @return {Ext.Element} this
15660          */
15661         setVisible : function(visible, animate){
15662             var me = this, isDisplay, isVisibility, isOffsets, isNosize,
15663                 dom = me.dom,
15664                 visMode = getVisMode(dom);
15665
15666
15667             // hideMode string override
15668             if (typeof animate == 'string'){
15669                 switch (animate) {
15670                     case DISPLAY:
15671                         visMode = El.DISPLAY;
15672                         break;
15673                     case VISIBILITY:
15674                         visMode = El.VISIBILITY;
15675                         break;
15676                     case OFFSETS:
15677                         visMode = El.OFFSETS;
15678                         break;
15679                     case NOSIZE:
15680                     case ASCLASS:
15681                         visMode = El.ASCLASS;
15682                         break;
15683                 }
15684                 me.setVisibilityMode(visMode);
15685                 animate = false;
15686             }
15687
15688             if (!animate || !me.anim) {
15689                 if(visMode == El.ASCLASS ){
15690
15691                     me[visible?'removeCls':'addCls'](me.visibilityCls || El.visibilityCls);
15692
15693                 } else if (visMode == El.DISPLAY){
15694
15695                     return me.setDisplayed(visible);
15696
15697                 } else if (visMode == El.OFFSETS){
15698
15699                     if (!visible){
15700                         // Remember position for restoring, if we are not already hidden by offsets.
15701                         if (!me.hideModeStyles) {
15702                             me.hideModeStyles = {
15703                                 position: me.getStyle('position'),
15704                                 top: me.getStyle('top'),
15705                                 left: me.getStyle('left')
15706                             };
15707                         }
15708                         me.applyStyles({position: 'absolute', top: '-10000px', left: '-10000px'});
15709                     }
15710
15711                     // Only "restore" as position if we have actually been hidden using offsets.
15712                     // Calling setVisible(true) on a positioned element should not reposition it.
15713                     else if (me.hideModeStyles) {
15714                         me.applyStyles(me.hideModeStyles || {position: '', top: '', left: ''});
15715                         delete me.hideModeStyles;
15716                     }
15717
15718                 }else{
15719                     me.fixDisplay();
15720                     // Show by clearing visibility style. Explicitly setting to "visible" overrides parent visibility setting.
15721                     dom.style.visibility = visible ? '' : HIDDEN;
15722                 }
15723             }else{
15724                 // closure for composites
15725                 if(visible){
15726                     me.setOpacity(0.01);
15727                     me.setVisible(true);
15728                 }
15729                 if (!Ext.isObject(animate)) {
15730                     animate = {
15731                         duration: 350,
15732                         easing: 'ease-in'
15733                     };
15734                 }
15735                 me.animate(Ext.applyIf({
15736                     callback: function() {
15737                         visible || me.setVisible(false).setOpacity(1);
15738                     },
15739                     to: {
15740                         opacity: (visible) ? 1 : 0
15741                     }
15742                 }, animate));
15743             }
15744             data(dom, ISVISIBLE, visible);  //set logical visibility state
15745             return me;
15746         },
15747
15748
15749         /**
15750          * @private
15751          * Determine if the Element has a relevant height and width available based
15752          * upon current logical visibility state
15753          */
15754         hasMetrics  : function(){
15755             var dom = this.dom;
15756             return this.isVisible() || (getVisMode(dom) == El.OFFSETS) || (getVisMode(dom) == El.VISIBILITY);
15757         },
15758
15759         /**
15760          * Toggles the element's visibility or display, depending on visibility mode.
15761          * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
15762          * @return {Ext.Element} this
15763          */
15764         toggle : function(animate){
15765             var me = this;
15766             me.setVisible(!me.isVisible(), me.anim(animate));
15767             return me;
15768         },
15769
15770         /**
15771          * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
15772          * @param {Boolean/String} value Boolean value to display the element using its default display, or a string to set the display directly.
15773          * @return {Ext.Element} this
15774          */
15775         setDisplayed : function(value) {
15776             if(typeof value == "boolean"){
15777                value = value ? getDisplay(this.dom) : NONE;
15778             }
15779             this.setStyle(DISPLAY, value);
15780             return this;
15781         },
15782
15783         // private
15784         fixDisplay : function(){
15785             var me = this;
15786             if (me.isStyle(DISPLAY, NONE)) {
15787                 me.setStyle(VISIBILITY, HIDDEN);
15788                 me.setStyle(DISPLAY, getDisplay(this.dom)); // first try reverting to default
15789                 if (me.isStyle(DISPLAY, NONE)) { // if that fails, default to block
15790                     me.setStyle(DISPLAY, "block");
15791                 }
15792             }
15793         },
15794
15795         /**
15796          * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
15797          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15798          * @return {Ext.Element} this
15799          */
15800         hide : function(animate){
15801             // hideMode override
15802             if (typeof animate == 'string'){
15803                 this.setVisible(false, animate);
15804                 return this;
15805             }
15806             this.setVisible(false, this.anim(animate));
15807             return this;
15808         },
15809
15810         /**
15811         * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
15812         * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15813          * @return {Ext.Element} this
15814          */
15815         show : function(animate){
15816             // hideMode override
15817             if (typeof animate == 'string'){
15818                 this.setVisible(true, animate);
15819                 return this;
15820             }
15821             this.setVisible(true, this.anim(animate));
15822             return this;
15823         }
15824     };
15825 }());
15826 /**
15827  * @class Ext.Element
15828  */
15829 Ext.applyIf(Ext.Element.prototype, {
15830     // @private override base Ext.util.Animate mixin for animate for backwards compatibility
15831     animate: function(config) {
15832         var me = this;
15833         if (!me.id) {
15834             me = Ext.get(me.dom);
15835         }
15836         if (Ext.fx.Manager.hasFxBlock(me.id)) {
15837             return me;
15838         }
15839         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(config)));
15840         return this;
15841     },
15842
15843     // @private override base Ext.util.Animate mixin for animate for backwards compatibility
15844     anim: function(config) {
15845         if (!Ext.isObject(config)) {
15846             return (config) ? {} : false;
15847         }
15848
15849         var me = this,
15850             duration = config.duration || Ext.fx.Anim.prototype.duration,
15851             easing = config.easing || 'ease',
15852             animConfig;
15853
15854         if (config.stopAnimation) {
15855             me.stopAnimation();
15856         }
15857
15858         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
15859
15860         // Clear any 'paused' defaults.
15861         Ext.fx.Manager.setFxDefaults(me.id, {
15862             delay: 0
15863         });
15864
15865         animConfig = {
15866             target: me,
15867             remove: config.remove,
15868             alternate: config.alternate || false,
15869             duration: duration,
15870             easing: easing,
15871             callback: config.callback,
15872             listeners: config.listeners,
15873             iterations: config.iterations || 1,
15874             scope: config.scope,
15875             block: config.block,
15876             concurrent: config.concurrent,
15877             delay: config.delay || 0,
15878             paused: true,
15879             keyframes: config.keyframes,
15880             from: config.from || {},
15881             to: Ext.apply({}, config)
15882         };
15883         Ext.apply(animConfig.to, config.to);
15884
15885         // Anim API properties - backward compat
15886         delete animConfig.to.to;
15887         delete animConfig.to.from;
15888         delete animConfig.to.remove;
15889         delete animConfig.to.alternate;
15890         delete animConfig.to.keyframes;
15891         delete animConfig.to.iterations;
15892         delete animConfig.to.listeners;
15893         delete animConfig.to.target;
15894         delete animConfig.to.paused;
15895         delete animConfig.to.callback;
15896         delete animConfig.to.scope;
15897         delete animConfig.to.duration;
15898         delete animConfig.to.easing;
15899         delete animConfig.to.concurrent;
15900         delete animConfig.to.block;
15901         delete animConfig.to.stopAnimation;
15902         delete animConfig.to.delay;
15903         return animConfig;
15904     },
15905
15906     /**
15907      * Slides the element into view. An anchor point can be optionally passed to set the point of origin for the slide
15908      * effect. This function automatically handles wrapping the element with a fixed-size container if needed. See the
15909      * Fx class overview for valid anchor point options. Usage:
15910      *
15911      *     // default: slide the element in from the top
15912      *     el.slideIn();
15913      *
15914      *     // custom: slide the element in from the right with a 2-second duration
15915      *     el.slideIn('r', { duration: 2000 });
15916      *
15917      *     // common config options shown with default values
15918      *     el.slideIn('t', {
15919      *         easing: 'easeOut',
15920      *         duration: 500
15921      *     });
15922      *
15923      * @param {String} [anchor='t'] One of the valid Fx anchor positions
15924      * @param {Object} [options] Object literal with any of the Fx config options
15925      * @return {Ext.Element} The Element
15926      */
15927     slideIn: function(anchor, obj, slideOut) {
15928         var me = this,
15929             elStyle = me.dom.style,
15930             beforeAnim, wrapAnim;
15931
15932         anchor = anchor || "t";
15933         obj = obj || {};
15934
15935         beforeAnim = function() {
15936             var animScope = this,
15937                 listeners = obj.listeners,
15938                 box, position, restoreSize, wrap, anim;
15939
15940             if (!slideOut) {
15941                 me.fixDisplay();
15942             }
15943
15944             box = me.getBox();
15945             if ((anchor == 't' || anchor == 'b') && box.height === 0) {
15946                 box.height = me.dom.scrollHeight;
15947             }
15948             else if ((anchor == 'l' || anchor == 'r') && box.width === 0) {
15949                 box.width = me.dom.scrollWidth;
15950             }
15951
15952             position = me.getPositioning();
15953             me.setSize(box.width, box.height);
15954
15955             wrap = me.wrap({
15956                 style: {
15957                     visibility: slideOut ? 'visible' : 'hidden'
15958                 }
15959             });
15960             wrap.setPositioning(position);
15961             if (wrap.isStyle('position', 'static')) {
15962                 wrap.position('relative');
15963             }
15964             me.clearPositioning('auto');
15965             wrap.clip();
15966
15967             // This element is temporarily positioned absolute within its wrapper.
15968             // Restore to its default, CSS-inherited visibility setting.
15969             // We cannot explicitly poke visibility:visible into its style because that overrides the visibility of the wrap.
15970             me.setStyle({
15971                 visibility: '',
15972                 position: 'absolute'
15973             });
15974             if (slideOut) {
15975                 wrap.setSize(box.width, box.height);
15976             }
15977
15978             switch (anchor) {
15979                 case 't':
15980                     anim = {
15981                         from: {
15982                             width: box.width + 'px',
15983                             height: '0px'
15984                         },
15985                         to: {
15986                             width: box.width + 'px',
15987                             height: box.height + 'px'
15988                         }
15989                     };
15990                     elStyle.bottom = '0px';
15991                     break;
15992                 case 'l':
15993                     anim = {
15994                         from: {
15995                             width: '0px',
15996                             height: box.height + 'px'
15997                         },
15998                         to: {
15999                             width: box.width + 'px',
16000                             height: box.height + 'px'
16001                         }
16002                     };
16003                     elStyle.right = '0px';
16004                     break;
16005                 case 'r':
16006                     anim = {
16007                         from: {
16008                             x: box.x + box.width,
16009                             width: '0px',
16010                             height: box.height + 'px'
16011                         },
16012                         to: {
16013                             x: box.x,
16014                             width: box.width + 'px',
16015                             height: box.height + 'px'
16016                         }
16017                     };
16018                     break;
16019                 case 'b':
16020                     anim = {
16021                         from: {
16022                             y: box.y + box.height,
16023                             width: box.width + 'px',
16024                             height: '0px'
16025                         },
16026                         to: {
16027                             y: box.y,
16028                             width: box.width + 'px',
16029                             height: box.height + 'px'
16030                         }
16031                     };
16032                     break;
16033                 case 'tl':
16034                     anim = {
16035                         from: {
16036                             x: box.x,
16037                             y: box.y,
16038                             width: '0px',
16039                             height: '0px'
16040                         },
16041                         to: {
16042                             width: box.width + 'px',
16043                             height: box.height + 'px'
16044                         }
16045                     };
16046                     elStyle.bottom = '0px';
16047                     elStyle.right = '0px';
16048                     break;
16049                 case 'bl':
16050                     anim = {
16051                         from: {
16052                             x: box.x + box.width,
16053                             width: '0px',
16054                             height: '0px'
16055                         },
16056                         to: {
16057                             x: box.x,
16058                             width: box.width + 'px',
16059                             height: box.height + 'px'
16060                         }
16061                     };
16062                     elStyle.right = '0px';
16063                     break;
16064                 case 'br':
16065                     anim = {
16066                         from: {
16067                             x: box.x + box.width,
16068                             y: box.y + box.height,
16069                             width: '0px',
16070                             height: '0px'
16071                         },
16072                         to: {
16073                             x: box.x,
16074                             y: box.y,
16075                             width: box.width + 'px',
16076                             height: box.height + 'px'
16077                         }
16078                     };
16079                     break;
16080                 case 'tr':
16081                     anim = {
16082                         from: {
16083                             y: box.y + box.height,
16084                             width: '0px',
16085                             height: '0px'
16086                         },
16087                         to: {
16088                             y: box.y,
16089                             width: box.width + 'px',
16090                             height: box.height + 'px'
16091                         }
16092                     };
16093                     elStyle.bottom = '0px';
16094                     break;
16095             }
16096
16097             wrap.show();
16098             wrapAnim = Ext.apply({}, obj);
16099             delete wrapAnim.listeners;
16100             wrapAnim = Ext.create('Ext.fx.Anim', Ext.applyIf(wrapAnim, {
16101                 target: wrap,
16102                 duration: 500,
16103                 easing: 'ease-out',
16104                 from: slideOut ? anim.to : anim.from,
16105                 to: slideOut ? anim.from : anim.to
16106             }));
16107
16108             // In the absence of a callback, this listener MUST be added first
16109             wrapAnim.on('afteranimate', function() {
16110                 if (slideOut) {
16111                     me.setPositioning(position);
16112                     if (obj.useDisplay) {
16113                         me.setDisplayed(false);
16114                     } else {
16115                         me.hide();
16116                     }
16117                 }
16118                 else {
16119                     me.clearPositioning();
16120                     me.setPositioning(position);
16121                 }
16122                 if (wrap.dom) {
16123                     wrap.dom.parentNode.insertBefore(me.dom, wrap.dom);
16124                     wrap.remove();
16125                 }
16126                 me.setSize(box.width, box.height);
16127                 animScope.end();
16128             });
16129             // Add configured listeners after
16130             if (listeners) {
16131                 wrapAnim.on(listeners);
16132             }
16133         };
16134
16135         me.animate({
16136             duration: obj.duration ? obj.duration * 2 : 1000,
16137             listeners: {
16138                 beforeanimate: {
16139                     fn: beforeAnim
16140                 },
16141                 afteranimate: {
16142                     fn: function() {
16143                         if (wrapAnim && wrapAnim.running) {
16144                             wrapAnim.end();
16145                         }
16146                     }
16147                 }
16148             }
16149         });
16150         return me;
16151     },
16152
16153
16154     /**
16155      * Slides the element out of view. An anchor point can be optionally passed to set the end point for the slide
16156      * effect. When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will
16157      * still take up space in the document. The element must be removed from the DOM using the 'remove' config option if
16158      * desired. This function automatically handles wrapping the element with a fixed-size container if needed. See the
16159      * Fx class overview for valid anchor point options. Usage:
16160      *
16161      *     // default: slide the element out to the top
16162      *     el.slideOut();
16163      *
16164      *     // custom: slide the element out to the right with a 2-second duration
16165      *     el.slideOut('r', { duration: 2000 });
16166      *
16167      *     // common config options shown with default values
16168      *     el.slideOut('t', {
16169      *         easing: 'easeOut',
16170      *         duration: 500,
16171      *         remove: false,
16172      *         useDisplay: false
16173      *     });
16174      *
16175      * @param {String} [anchor='t'] One of the valid Fx anchor positions
16176      * @param {Object} [options] Object literal with any of the Fx config options
16177      * @return {Ext.Element} The Element
16178      */
16179     slideOut: function(anchor, o) {
16180         return this.slideIn(anchor, o, true);
16181     },
16182
16183     /**
16184      * Fades the element out while slowly expanding it in all directions. When the effect is completed, the element will
16185      * be hidden (visibility = 'hidden') but block elements will still take up space in the document. Usage:
16186      *
16187      *     // default
16188      *     el.puff();
16189      *
16190      *     // common config options shown with default values
16191      *     el.puff({
16192      *         easing: 'easeOut',
16193      *         duration: 500,
16194      *         useDisplay: false
16195      *     });
16196      *
16197      * @param {Object} options (optional) Object literal with any of the Fx config options
16198      * @return {Ext.Element} The Element
16199      */
16200     puff: function(obj) {
16201         var me = this,
16202             beforeAnim;
16203         obj = Ext.applyIf(obj || {}, {
16204             easing: 'ease-out',
16205             duration: 500,
16206             useDisplay: false
16207         });
16208
16209         beforeAnim = function() {
16210             me.clearOpacity();
16211             me.show();
16212
16213             var box = me.getBox(),
16214                 fontSize = me.getStyle('fontSize'),
16215                 position = me.getPositioning();
16216             this.to = {
16217                 width: box.width * 2,
16218                 height: box.height * 2,
16219                 x: box.x - (box.width / 2),
16220                 y: box.y - (box.height /2),
16221                 opacity: 0,
16222                 fontSize: '200%'
16223             };
16224             this.on('afteranimate',function() {
16225                 if (me.dom) {
16226                     if (obj.useDisplay) {
16227                         me.setDisplayed(false);
16228                     } else {
16229                         me.hide();
16230                     }
16231                     me.clearOpacity();
16232                     me.setPositioning(position);
16233                     me.setStyle({fontSize: fontSize});
16234                 }
16235             });
16236         };
16237
16238         me.animate({
16239             duration: obj.duration,
16240             easing: obj.easing,
16241             listeners: {
16242                 beforeanimate: {
16243                     fn: beforeAnim
16244                 }
16245             }
16246         });
16247         return me;
16248     },
16249
16250     /**
16251      * Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television).
16252      * When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still
16253      * take up space in the document. The element must be removed from the DOM using the 'remove' config option if
16254      * desired. Usage:
16255      *
16256      *     // default
16257      *     el.switchOff();
16258      *
16259      *     // all config options shown with default values
16260      *     el.switchOff({
16261      *         easing: 'easeIn',
16262      *         duration: .3,
16263      *         remove: false,
16264      *         useDisplay: false
16265      *     });
16266      *
16267      * @param {Object} options (optional) Object literal with any of the Fx config options
16268      * @return {Ext.Element} The Element
16269      */
16270     switchOff: function(obj) {
16271         var me = this,
16272             beforeAnim;
16273
16274         obj = Ext.applyIf(obj || {}, {
16275             easing: 'ease-in',
16276             duration: 500,
16277             remove: false,
16278             useDisplay: false
16279         });
16280
16281         beforeAnim = function() {
16282             var animScope = this,
16283                 size = me.getSize(),
16284                 xy = me.getXY(),
16285                 keyframe, position;
16286             me.clearOpacity();
16287             me.clip();
16288             position = me.getPositioning();
16289
16290             keyframe = Ext.create('Ext.fx.Animator', {
16291                 target: me,
16292                 duration: obj.duration,
16293                 easing: obj.easing,
16294                 keyframes: {
16295                     33: {
16296                         opacity: 0.3
16297                     },
16298                     66: {
16299                         height: 1,
16300                         y: xy[1] + size.height / 2
16301                     },
16302                     100: {
16303                         width: 1,
16304                         x: xy[0] + size.width / 2
16305                     }
16306                 }
16307             });
16308             keyframe.on('afteranimate', function() {
16309                 if (obj.useDisplay) {
16310                     me.setDisplayed(false);
16311                 } else {
16312                     me.hide();
16313                 }
16314                 me.clearOpacity();
16315                 me.setPositioning(position);
16316                 me.setSize(size);
16317                 animScope.end();
16318             });
16319         };
16320         me.animate({
16321             duration: (obj.duration * 2),
16322             listeners: {
16323                 beforeanimate: {
16324                     fn: beforeAnim
16325                 }
16326             }
16327         });
16328         return me;
16329     },
16330
16331     /**
16332      * Shows a ripple of exploding, attenuating borders to draw attention to an Element. Usage:
16333      *
16334      *     // default: a single light blue ripple
16335      *     el.frame();
16336      *
16337      *     // custom: 3 red ripples lasting 3 seconds total
16338      *     el.frame("#ff0000", 3, { duration: 3 });
16339      *
16340      *     // common config options shown with default values
16341      *     el.frame("#C3DAF9", 1, {
16342      *         duration: 1 //duration of each individual ripple.
16343      *         // Note: Easing is not configurable and will be ignored if included
16344      *     });
16345      *
16346      * @param {String} [color='C3DAF9'] The color of the border. Should be a 6 char hex color without the leading #
16347      * (defaults to light blue).
16348      * @param {Number} [count=1] The number of ripples to display
16349      * @param {Object} [options] Object literal with any of the Fx config options
16350      * @return {Ext.Element} The Element
16351      */
16352     frame : function(color, count, obj){
16353         var me = this,
16354             beforeAnim;
16355
16356         color = color || '#C3DAF9';
16357         count = count || 1;
16358         obj = obj || {};
16359
16360         beforeAnim = function() {
16361             me.show();
16362             var animScope = this,
16363                 box = me.getBox(),
16364                 proxy = Ext.getBody().createChild({
16365                     style: {
16366                         position : 'absolute',
16367                         'pointer-events': 'none',
16368                         'z-index': 35000,
16369                         border : '0px solid ' + color
16370                     }
16371                 }),
16372                 proxyAnim;
16373             proxyAnim = Ext.create('Ext.fx.Anim', {
16374                 target: proxy,
16375                 duration: obj.duration || 1000,
16376                 iterations: count,
16377                 from: {
16378                     top: box.y,
16379                     left: box.x,
16380                     borderWidth: 0,
16381                     opacity: 1,
16382                     height: box.height,
16383                     width: box.width
16384                 },
16385                 to: {
16386                     top: box.y - 20,
16387                     left: box.x - 20,
16388                     borderWidth: 10,
16389                     opacity: 0,
16390                     height: box.height + 40,
16391                     width: box.width + 40
16392                 }
16393             });
16394             proxyAnim.on('afteranimate', function() {
16395                 proxy.remove();
16396                 animScope.end();
16397             });
16398         };
16399
16400         me.animate({
16401             duration: (obj.duration * 2) || 2000,
16402             listeners: {
16403                 beforeanimate: {
16404                     fn: beforeAnim
16405                 }
16406             }
16407         });
16408         return me;
16409     },
16410
16411     /**
16412      * Slides the element while fading it out of view. An anchor point can be optionally passed to set the ending point
16413      * of the effect. Usage:
16414      *
16415      *     // default: slide the element downward while fading out
16416      *     el.ghost();
16417      *
16418      *     // custom: slide the element out to the right with a 2-second duration
16419      *     el.ghost('r', { duration: 2000 });
16420      *
16421      *     // common config options shown with default values
16422      *     el.ghost('b', {
16423      *         easing: 'easeOut',
16424      *         duration: 500
16425      *     });
16426      *
16427      * @param {String} [anchor='b'] One of the valid Fx anchor positions
16428      * @param {Object} [options] Object literal with any of the Fx config options
16429      * @return {Ext.Element} The Element
16430      */
16431     ghost: function(anchor, obj) {
16432         var me = this,
16433             beforeAnim;
16434
16435         anchor = anchor || "b";
16436         beforeAnim = function() {
16437             var width = me.getWidth(),
16438                 height = me.getHeight(),
16439                 xy = me.getXY(),
16440                 position = me.getPositioning(),
16441                 to = {
16442                     opacity: 0
16443                 };
16444             switch (anchor) {
16445                 case 't':
16446                     to.y = xy[1] - height;
16447                     break;
16448                 case 'l':
16449                     to.x = xy[0] - width;
16450                     break;
16451                 case 'r':
16452                     to.x = xy[0] + width;
16453                     break;
16454                 case 'b':
16455                     to.y = xy[1] + height;
16456                     break;
16457                 case 'tl':
16458                     to.x = xy[0] - width;
16459                     to.y = xy[1] - height;
16460                     break;
16461                 case 'bl':
16462                     to.x = xy[0] - width;
16463                     to.y = xy[1] + height;
16464                     break;
16465                 case 'br':
16466                     to.x = xy[0] + width;
16467                     to.y = xy[1] + height;
16468                     break;
16469                 case 'tr':
16470                     to.x = xy[0] + width;
16471                     to.y = xy[1] - height;
16472                     break;
16473             }
16474             this.to = to;
16475             this.on('afteranimate', function () {
16476                 if (me.dom) {
16477                     me.hide();
16478                     me.clearOpacity();
16479                     me.setPositioning(position);
16480                 }
16481             });
16482         };
16483
16484         me.animate(Ext.applyIf(obj || {}, {
16485             duration: 500,
16486             easing: 'ease-out',
16487             listeners: {
16488                 beforeanimate: {
16489                     fn: beforeAnim
16490                 }
16491             }
16492         }));
16493         return me;
16494     },
16495
16496     /**
16497      * Highlights the Element by setting a color (applies to the background-color by default, but can be changed using
16498      * the "attr" config option) and then fading back to the original color. If no original color is available, you
16499      * should provide the "endColor" config option which will be cleared after the animation. Usage:
16500      *
16501      *     // default: highlight background to yellow
16502      *     el.highlight();
16503      *
16504      *     // custom: highlight foreground text to blue for 2 seconds
16505      *     el.highlight("0000ff", { attr: 'color', duration: 2000 });
16506      *
16507      *     // common config options shown with default values
16508      *     el.highlight("ffff9c", {
16509      *         attr: "backgroundColor", //can be any valid CSS property (attribute) that supports a color value
16510      *         endColor: (current color) or "ffffff",
16511      *         easing: 'easeIn',
16512      *         duration: 1000
16513      *     });
16514      *
16515      * @param {String} [color='ffff9c'] The highlight color. Should be a 6 char hex color without the leading #
16516      * @param {Object} [options] Object literal with any of the Fx config options
16517      * @return {Ext.Element} The Element
16518      */
16519     highlight: function(color, o) {
16520         var me = this,
16521             dom = me.dom,
16522             from = {},
16523             restore, to, attr, lns, event, fn;
16524
16525         o = o || {};
16526         lns = o.listeners || {};
16527         attr = o.attr || 'backgroundColor';
16528         from[attr] = color || 'ffff9c';
16529
16530         if (!o.to) {
16531             to = {};
16532             to[attr] = o.endColor || me.getColor(attr, 'ffffff', '');
16533         }
16534         else {
16535             to = o.to;
16536         }
16537
16538         // Don't apply directly on lns, since we reference it in our own callbacks below
16539         o.listeners = Ext.apply(Ext.apply({}, lns), {
16540             beforeanimate: function() {
16541                 restore = dom.style[attr];
16542                 me.clearOpacity();
16543                 me.show();
16544
16545                 event = lns.beforeanimate;
16546                 if (event) {
16547                     fn = event.fn || event;
16548                     return fn.apply(event.scope || lns.scope || window, arguments);
16549                 }
16550             },
16551             afteranimate: function() {
16552                 if (dom) {
16553                     dom.style[attr] = restore;
16554                 }
16555
16556                 event = lns.afteranimate;
16557                 if (event) {
16558                     fn = event.fn || event;
16559                     fn.apply(event.scope || lns.scope || window, arguments);
16560                 }
16561             }
16562         });
16563
16564         me.animate(Ext.apply({}, o, {
16565             duration: 1000,
16566             easing: 'ease-in',
16567             from: from,
16568             to: to
16569         }));
16570         return me;
16571     },
16572
16573    /**
16574     * @deprecated 4.0
16575     * Creates a pause before any subsequent queued effects begin. If there are no effects queued after the pause it will
16576     * have no effect. Usage:
16577     *
16578     *     el.pause(1);
16579     *
16580     * @param {Number} seconds The length of time to pause (in seconds)
16581     * @return {Ext.Element} The Element
16582     */
16583     pause: function(ms) {
16584         var me = this;
16585         Ext.fx.Manager.setFxDefaults(me.id, {
16586             delay: ms
16587         });
16588         return me;
16589     },
16590
16591     /**
16592      * Fade an element in (from transparent to opaque). The ending opacity can be specified using the `opacity`
16593      * config option. Usage:
16594      *
16595      *     // default: fade in from opacity 0 to 100%
16596      *     el.fadeIn();
16597      *
16598      *     // custom: fade in from opacity 0 to 75% over 2 seconds
16599      *     el.fadeIn({ opacity: .75, duration: 2000});
16600      *
16601      *     // common config options shown with default values
16602      *     el.fadeIn({
16603      *         opacity: 1, //can be any value between 0 and 1 (e.g. .5)
16604      *         easing: 'easeOut',
16605      *         duration: 500
16606      *     });
16607      *
16608      * @param {Object} options (optional) Object literal with any of the Fx config options
16609      * @return {Ext.Element} The Element
16610      */
16611     fadeIn: function(o) {
16612         this.animate(Ext.apply({}, o, {
16613             opacity: 1
16614         }));
16615         return this;
16616     },
16617
16618     /**
16619      * Fade an element out (from opaque to transparent). The ending opacity can be specified using the `opacity`
16620      * config option. Note that IE may require `useDisplay:true` in order to redisplay correctly.
16621      * Usage:
16622      *
16623      *     // default: fade out from the element's current opacity to 0
16624      *     el.fadeOut();
16625      *
16626      *     // custom: fade out from the element's current opacity to 25% over 2 seconds
16627      *     el.fadeOut({ opacity: .25, duration: 2000});
16628      *
16629      *     // common config options shown with default values
16630      *     el.fadeOut({
16631      *         opacity: 0, //can be any value between 0 and 1 (e.g. .5)
16632      *         easing: 'easeOut',
16633      *         duration: 500,
16634      *         remove: false,
16635      *         useDisplay: false
16636      *     });
16637      *
16638      * @param {Object} options (optional) Object literal with any of the Fx config options
16639      * @return {Ext.Element} The Element
16640      */
16641     fadeOut: function(o) {
16642         this.animate(Ext.apply({}, o, {
16643             opacity: 0
16644         }));
16645         return this;
16646     },
16647
16648     /**
16649      * @deprecated 4.0
16650      * Animates the transition of an element's dimensions from a starting height/width to an ending height/width. This
16651      * method is a convenience implementation of {@link #shift}. Usage:
16652      *
16653      *     // change height and width to 100x100 pixels
16654      *     el.scale(100, 100);
16655      *
16656      *     // common config options shown with default values.  The height and width will default to
16657      *     // the element's existing values if passed as null.
16658      *     el.scale(
16659      *         [element's width],
16660      *         [element's height], {
16661      *             easing: 'easeOut',
16662      *             duration: .35
16663      *         }
16664      *     );
16665      *
16666      * @param {Number} width The new width (pass undefined to keep the original width)
16667      * @param {Number} height The new height (pass undefined to keep the original height)
16668      * @param {Object} options (optional) Object literal with any of the Fx config options
16669      * @return {Ext.Element} The Element
16670      */
16671     scale: function(w, h, o) {
16672         this.animate(Ext.apply({}, o, {
16673             width: w,
16674             height: h
16675         }));
16676         return this;
16677     },
16678
16679     /**
16680      * @deprecated 4.0
16681      * Animates the transition of any combination of an element's dimensions, xy position and/or opacity. Any of these
16682      * properties not specified in the config object will not be changed. This effect requires that at least one new
16683      * dimension, position or opacity setting must be passed in on the config object in order for the function to have
16684      * any effect. Usage:
16685      *
16686      *     // slide the element horizontally to x position 200 while changing the height and opacity
16687      *     el.shift({ x: 200, height: 50, opacity: .8 });
16688      *
16689      *     // common config options shown with default values.
16690      *     el.shift({
16691      *         width: [element's width],
16692      *         height: [element's height],
16693      *         x: [element's x position],
16694      *         y: [element's y position],
16695      *         opacity: [element's opacity],
16696      *         easing: 'easeOut',
16697      *         duration: .35
16698      *     });
16699      *
16700      * @param {Object} options Object literal with any of the Fx config options
16701      * @return {Ext.Element} The Element
16702      */
16703     shift: function(config) {
16704         this.animate(config);
16705         return this;
16706     }
16707 });
16708
16709 /**
16710  * @class Ext.Element
16711  */
16712 Ext.applyIf(Ext.Element, {
16713     unitRe: /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
16714     camelRe: /(-[a-z])/gi,
16715     opacityRe: /alpha\(opacity=(.*)\)/i,
16716     cssRe: /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
16717     propertyCache: {},
16718     defaultUnit : "px",
16719     borders: {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'},
16720     paddings: {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'},
16721     margins: {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'},
16722
16723     // Reference the prototype's version of the method. Signatures are identical.
16724     addUnits : Ext.Element.prototype.addUnits,
16725
16726     /**
16727      * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
16728      * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
16729      * @static
16730      * @param {Number/String} box The encoded margins
16731      * @return {Object} An object with margin sizes for top, right, bottom and left
16732      */
16733     parseBox : function(box) {
16734         if (Ext.isObject(box)) {
16735             return {
16736                 top: box.top || 0,
16737                 right: box.right || 0,
16738                 bottom: box.bottom || 0,
16739                 left: box.left || 0
16740             };
16741         } else {
16742             if (typeof box != 'string') {
16743                 box = box.toString();
16744             }
16745             var parts  = box.split(' '),
16746                 ln = parts.length;
16747     
16748             if (ln == 1) {
16749                 parts[1] = parts[2] = parts[3] = parts[0];
16750             }
16751             else if (ln == 2) {
16752                 parts[2] = parts[0];
16753                 parts[3] = parts[1];
16754             }
16755             else if (ln == 3) {
16756                 parts[3] = parts[1];
16757             }
16758     
16759             return {
16760                 top   :parseFloat(parts[0]) || 0,
16761                 right :parseFloat(parts[1]) || 0,
16762                 bottom:parseFloat(parts[2]) || 0,
16763                 left  :parseFloat(parts[3]) || 0
16764             };
16765         }
16766         
16767     },
16768     
16769     /**
16770      * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
16771      * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
16772      * @static
16773      * @param {Number/String} box The encoded margins
16774      * @param {String} units The type of units to add
16775      * @return {String} An string with unitized (px if units is not specified) metrics for top, right, bottom and left
16776      */
16777     unitizeBox : function(box, units) {
16778         var A = this.addUnits,
16779             B = this.parseBox(box);
16780             
16781         return A(B.top, units) + ' ' +
16782                A(B.right, units) + ' ' +
16783                A(B.bottom, units) + ' ' +
16784                A(B.left, units);
16785         
16786     },
16787
16788     // private
16789     camelReplaceFn : function(m, a) {
16790         return a.charAt(1).toUpperCase();
16791     },
16792
16793     /**
16794      * Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax.
16795      * For example:
16796      * <ul>
16797      *  <li>border-width -> borderWidth</li>
16798      *  <li>padding-top -> paddingTop</li>
16799      * </ul>
16800      * @static
16801      * @param {String} prop The property to normalize
16802      * @return {String} The normalized string
16803      */
16804     normalize : function(prop) {
16805         if (prop == 'float') {
16806             prop = Ext.supports.Float ? 'cssFloat' : 'styleFloat';
16807         }
16808         return this.propertyCache[prop] || (this.propertyCache[prop] = prop.replace(this.camelRe, this.camelReplaceFn));
16809     },
16810
16811     /**
16812      * Retrieves the document height
16813      * @static
16814      * @return {Number} documentHeight
16815      */
16816     getDocumentHeight: function() {
16817         return Math.max(!Ext.isStrict ? document.body.scrollHeight : document.documentElement.scrollHeight, this.getViewportHeight());
16818     },
16819
16820     /**
16821      * Retrieves the document width
16822      * @static
16823      * @return {Number} documentWidth
16824      */
16825     getDocumentWidth: function() {
16826         return Math.max(!Ext.isStrict ? document.body.scrollWidth : document.documentElement.scrollWidth, this.getViewportWidth());
16827     },
16828
16829     /**
16830      * Retrieves the viewport height of the window.
16831      * @static
16832      * @return {Number} viewportHeight
16833      */
16834     getViewportHeight: function(){
16835         return window.innerHeight;
16836     },
16837
16838     /**
16839      * Retrieves the viewport width of the window.
16840      * @static
16841      * @return {Number} viewportWidth
16842      */
16843     getViewportWidth : function() {
16844         return window.innerWidth;
16845     },
16846
16847     /**
16848      * Retrieves the viewport size of the window.
16849      * @static
16850      * @return {Object} object containing width and height properties
16851      */
16852     getViewSize : function() {
16853         return {
16854             width: window.innerWidth,
16855             height: window.innerHeight
16856         };
16857     },
16858
16859     /**
16860      * Retrieves the current orientation of the window. This is calculated by
16861      * determing if the height is greater than the width.
16862      * @static
16863      * @return {String} Orientation of window: 'portrait' or 'landscape'
16864      */
16865     getOrientation : function() {
16866         if (Ext.supports.OrientationChange) {
16867             return (window.orientation == 0) ? 'portrait' : 'landscape';
16868         }
16869         
16870         return (window.innerHeight > window.innerWidth) ? 'portrait' : 'landscape';
16871     },
16872
16873     /** 
16874      * Returns the top Element that is located at the passed coordinates
16875      * @static
16876      * @param {Number} x The x coordinate
16877      * @param {Number} y The y coordinate
16878      * @return {String} The found Element
16879      */
16880     fromPoint: function(x, y) {
16881         return Ext.get(document.elementFromPoint(x, y));
16882     },
16883     
16884     /**
16885      * Converts a CSS string into an object with a property for each style.
16886      * <p>
16887      * The sample code below would return an object with 2 properties, one
16888      * for background-color and one for color.</p>
16889      * <pre><code>
16890 var css = 'background-color: red;color: blue; ';
16891 console.log(Ext.Element.parseStyles(css));
16892      * </code></pre>
16893      * @static
16894      * @param {String} styles A CSS string
16895      * @return {Object} styles
16896      */
16897     parseStyles: function(styles){
16898         var out = {},
16899             cssRe = this.cssRe,
16900             matches;
16901             
16902         if (styles) {
16903             // Since we're using the g flag on the regex, we need to set the lastIndex.
16904             // This automatically happens on some implementations, but not others, see:
16905             // http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
16906             // http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
16907             cssRe.lastIndex = 0;
16908             while ((matches = cssRe.exec(styles))) {
16909                 out[matches[1]] = matches[2];
16910             }
16911         }
16912         return out;
16913     }
16914 });
16915
16916 /**
16917  * @class Ext.CompositeElementLite
16918  * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
16919  * members, or to perform collective actions upon the whole set.</p>
16920  * <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
16921  * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
16922  * Example:<pre><code>
16923 var els = Ext.select("#some-el div.some-class");
16924 // or select directly from an existing element
16925 var el = Ext.get('some-el');
16926 el.select('div.some-class');
16927
16928 els.setWidth(100); // all elements become 100 width
16929 els.hide(true); // all elements fade out and hide
16930 // or
16931 els.setWidth(100).hide(true);
16932 </code></pre>
16933  */
16934 Ext.CompositeElementLite = function(els, root){
16935     /**
16936      * <p>The Array of DOM elements which this CompositeElement encapsulates. Read-only.</p>
16937      * <p>This will not <i>usually</i> be accessed in developers' code, but developers wishing
16938      * to augment the capabilities of the CompositeElementLite class may use it when adding
16939      * methods to the class.</p>
16940      * <p>For example to add the <code>nextAll</code> method to the class to <b>add</b> all
16941      * following siblings of selected elements, the code would be</p><code><pre>
16942 Ext.override(Ext.CompositeElementLite, {
16943     nextAll: function() {
16944         var els = this.elements, i, l = els.length, n, r = [], ri = -1;
16945
16946 //      Loop through all elements in this Composite, accumulating
16947 //      an Array of all siblings.
16948         for (i = 0; i < l; i++) {
16949             for (n = els[i].nextSibling; n; n = n.nextSibling) {
16950                 r[++ri] = n;
16951             }
16952         }
16953
16954 //      Add all found siblings to this Composite
16955         return this.add(r);
16956     }
16957 });</pre></code>
16958      * @property {HTMLElement} elements
16959      */
16960     this.elements = [];
16961     this.add(els, root);
16962     this.el = new Ext.Element.Flyweight();
16963 };
16964
16965 Ext.CompositeElementLite.prototype = {
16966     isComposite: true,
16967
16968     // private
16969     getElement : function(el){
16970         // Set the shared flyweight dom property to the current element
16971         var e = this.el;
16972         e.dom = el;
16973         e.id = el.id;
16974         return e;
16975     },
16976
16977     // private
16978     transformElement : function(el){
16979         return Ext.getDom(el);
16980     },
16981
16982     /**
16983      * Returns the number of elements in this Composite.
16984      * @return Number
16985      */
16986     getCount : function(){
16987         return this.elements.length;
16988     },
16989     /**
16990      * Adds elements to this Composite object.
16991      * @param {HTMLElement[]/Ext.CompositeElement} els Either an Array of DOM elements to add, or another Composite object who's elements should be added.
16992      * @return {Ext.CompositeElement} This Composite object.
16993      */
16994     add : function(els, root){
16995         var me = this,
16996             elements = me.elements;
16997         if(!els){
16998             return this;
16999         }
17000         if(typeof els == "string"){
17001             els = Ext.Element.selectorFunction(els, root);
17002         }else if(els.isComposite){
17003             els = els.elements;
17004         }else if(!Ext.isIterable(els)){
17005             els = [els];
17006         }
17007
17008         for(var i = 0, len = els.length; i < len; ++i){
17009             elements.push(me.transformElement(els[i]));
17010         }
17011         return me;
17012     },
17013
17014     invoke : function(fn, args){
17015         var me = this,
17016             els = me.elements,
17017             len = els.length,
17018             e,
17019             i;
17020
17021         for(i = 0; i < len; i++) {
17022             e = els[i];
17023             if(e){
17024                 Ext.Element.prototype[fn].apply(me.getElement(e), args);
17025             }
17026         }
17027         return me;
17028     },
17029     /**
17030      * Returns a flyweight Element of the dom element object at the specified index
17031      * @param {Number} index
17032      * @return {Ext.Element}
17033      */
17034     item : function(index){
17035         var me = this,
17036             el = me.elements[index],
17037             out = null;
17038
17039         if(el){
17040             out = me.getElement(el);
17041         }
17042         return out;
17043     },
17044
17045     // fixes scope with flyweight
17046     addListener : function(eventName, handler, scope, opt){
17047         var els = this.elements,
17048             len = els.length,
17049             i, e;
17050
17051         for(i = 0; i<len; i++) {
17052             e = els[i];
17053             if(e) {
17054                 Ext.EventManager.on(e, eventName, handler, scope || e, opt);
17055             }
17056         }
17057         return this;
17058     },
17059     /**
17060      * <p>Calls the passed function for each element in this composite.</p>
17061      * @param {Function} fn The function to call. The function is passed the following parameters:<ul>
17062      * <li><b>el</b> : Element<div class="sub-desc">The current Element in the iteration.
17063      * <b>This is the flyweight (shared) Ext.Element instance, so if you require a
17064      * a reference to the dom node, use el.dom.</b></div></li>
17065      * <li><b>c</b> : Composite<div class="sub-desc">This Composite object.</div></li>
17066      * <li><b>idx</b> : Number<div class="sub-desc">The zero-based index in the iteration.</div></li>
17067      * </ul>
17068      * @param {Object} [scope] The scope (<i>this</i> reference) in which the function is executed. (defaults to the Element)
17069      * @return {Ext.CompositeElement} this
17070      */
17071     each : function(fn, scope){
17072         var me = this,
17073             els = me.elements,
17074             len = els.length,
17075             i, e;
17076
17077         for(i = 0; i<len; i++) {
17078             e = els[i];
17079             if(e){
17080                 e = this.getElement(e);
17081                 if(fn.call(scope || e, e, me, i) === false){
17082                     break;
17083                 }
17084             }
17085         }
17086         return me;
17087     },
17088
17089     /**
17090     * Clears this Composite and adds the elements passed.
17091     * @param {HTMLElement[]/Ext.CompositeElement} els Either an array of DOM elements, or another Composite from which to fill this Composite.
17092     * @return {Ext.CompositeElement} this
17093     */
17094     fill : function(els){
17095         var me = this;
17096         me.elements = [];
17097         me.add(els);
17098         return me;
17099     },
17100
17101     /**
17102      * Filters this composite to only elements that match the passed selector.
17103      * @param {String/Function} selector A string CSS selector or a comparison function.
17104      * The comparison function will be called with the following arguments:<ul>
17105      * <li><code>el</code> : Ext.Element<div class="sub-desc">The current DOM element.</div></li>
17106      * <li><code>index</code> : Number<div class="sub-desc">The current index within the collection.</div></li>
17107      * </ul>
17108      * @return {Ext.CompositeElement} this
17109      */
17110     filter : function(selector){
17111         var els = [],
17112             me = this,
17113             fn = Ext.isFunction(selector) ? selector
17114                 : function(el){
17115                     return el.is(selector);
17116                 };
17117
17118         me.each(function(el, self, i) {
17119             if (fn(el, i) !== false) {
17120                 els[els.length] = me.transformElement(el);
17121             }
17122         });
17123
17124         me.elements = els;
17125         return me;
17126     },
17127
17128     /**
17129      * Find the index of the passed element within the composite collection.
17130      * @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
17131      * @return Number The index of the passed Ext.Element in the composite collection, or -1 if not found.
17132      */
17133     indexOf : function(el){
17134         return Ext.Array.indexOf(this.elements, this.transformElement(el));
17135     },
17136
17137     /**
17138     * Replaces the specified element with the passed element.
17139     * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the element in this composite
17140     * to replace.
17141     * @param {String/Ext.Element} replacement The id of an element or the Element itself.
17142     * @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
17143     * @return {Ext.CompositeElement} this
17144     */
17145     replaceElement : function(el, replacement, domReplace){
17146         var index = !isNaN(el) ? el : this.indexOf(el),
17147             d;
17148         if(index > -1){
17149             replacement = Ext.getDom(replacement);
17150             if(domReplace){
17151                 d = this.elements[index];
17152                 d.parentNode.insertBefore(replacement, d);
17153                 Ext.removeNode(d);
17154             }
17155             Ext.Array.splice(this.elements, index, 1, replacement);
17156         }
17157         return this;
17158     },
17159
17160     /**
17161      * Removes all elements.
17162      */
17163     clear : function(){
17164         this.elements = [];
17165     }
17166 };
17167
17168 Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
17169
17170 /**
17171  * @private
17172  * Copies all of the functions from Ext.Element's prototype onto CompositeElementLite's prototype.
17173  * This is called twice - once immediately below, and once again after additional Ext.Element
17174  * are added in Ext JS
17175  */
17176 Ext.CompositeElementLite.importElementMethods = function() {
17177     var fnName,
17178         ElProto = Ext.Element.prototype,
17179         CelProto = Ext.CompositeElementLite.prototype;
17180
17181     for (fnName in ElProto) {
17182         if (typeof ElProto[fnName] == 'function'){
17183             (function(fnName) {
17184                 CelProto[fnName] = CelProto[fnName] || function() {
17185                     return this.invoke(fnName, arguments);
17186                 };
17187             }).call(CelProto, fnName);
17188
17189         }
17190     }
17191 };
17192
17193 Ext.CompositeElementLite.importElementMethods();
17194
17195 if(Ext.DomQuery){
17196     Ext.Element.selectorFunction = Ext.DomQuery.select;
17197 }
17198
17199 /**
17200  * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
17201  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
17202  * {@link Ext.CompositeElementLite CompositeElementLite} object.
17203  * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
17204  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
17205  * @return {Ext.CompositeElementLite/Ext.CompositeElement}
17206  * @member Ext.Element
17207  * @method select
17208  */
17209 Ext.Element.select = function(selector, root){
17210     var els;
17211     if(typeof selector == "string"){
17212         els = Ext.Element.selectorFunction(selector, root);
17213     }else if(selector.length !== undefined){
17214         els = selector;
17215     }else{
17216         Ext.Error.raise({
17217             sourceClass: "Ext.Element",
17218             sourceMethod: "select",
17219             selector: selector,
17220             root: root,
17221             msg: "Invalid selector specified: " + selector
17222         });
17223     }
17224     return new Ext.CompositeElementLite(els);
17225 };
17226 /**
17227  * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
17228  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
17229  * {@link Ext.CompositeElementLite CompositeElementLite} object.
17230  * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
17231  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
17232  * @return {Ext.CompositeElementLite/Ext.CompositeElement}
17233  * @member Ext
17234  * @method select
17235  */
17236 Ext.select = Ext.Element.select;
17237
17238 /**
17239  * @class Ext.util.DelayedTask
17240  * 
17241  * The DelayedTask class provides a convenient way to "buffer" the execution of a method,
17242  * performing setTimeout where a new timeout cancels the old timeout. When called, the
17243  * task will wait the specified time period before executing. If durng that time period,
17244  * the task is called again, the original call will be cancelled. This continues so that
17245  * the function is only called a single time for each iteration.
17246  * 
17247  * This method is especially useful for things like detecting whether a user has finished
17248  * typing in a text field. An example would be performing validation on a keypress. You can
17249  * use this class to buffer the keypress events for a certain number of milliseconds, and
17250  * perform only if they stop for that amount of time.  
17251  * 
17252  * ## Usage
17253  * 
17254  *     var task = new Ext.util.DelayedTask(function(){
17255  *         alert(Ext.getDom('myInputField').value.length);
17256  *     });
17257  *     
17258  *     // Wait 500ms before calling our function. If the user presses another key
17259  *     // during that 500ms, it will be cancelled and we'll wait another 500ms.
17260  *     Ext.get('myInputField').on('keypress', function(){
17261  *         task.{@link #delay}(500);
17262  *     });
17263  * 
17264  * Note that we are using a DelayedTask here to illustrate a point. The configuration
17265  * option `buffer` for {@link Ext.util.Observable#addListener addListener/on} will
17266  * also setup a delayed task for you to buffer events.
17267  * 
17268  * @constructor The parameters to this constructor serve as defaults and are not required.
17269  * @param {Function} fn (optional) The default function to call. If not specified here, it must be specified during the {@link #delay} call.
17270  * @param {Object} scope (optional) The default scope (The <code><b>this</b></code> reference) in which the
17271  * function is called. If not specified, <code>this</code> will refer to the browser window.
17272  * @param {Array} args (optional) The default Array of arguments.
17273  */
17274 Ext.util.DelayedTask = function(fn, scope, args) {
17275     var me = this,
17276         id,
17277         call = function() {
17278             clearInterval(id);
17279             id = null;
17280             fn.apply(scope, args || []);
17281         };
17282
17283     /**
17284      * Cancels any pending timeout and queues a new one
17285      * @param {Number} delay The milliseconds to delay
17286      * @param {Function} newFn (optional) Overrides function passed to constructor
17287      * @param {Object} newScope (optional) Overrides scope passed to constructor. Remember that if no scope
17288      * is specified, <code>this</code> will refer to the browser window.
17289      * @param {Array} newArgs (optional) Overrides args passed to constructor
17290      */
17291     this.delay = function(delay, newFn, newScope, newArgs) {
17292         me.cancel();
17293         fn = newFn || fn;
17294         scope = newScope || scope;
17295         args = newArgs || args;
17296         id = setInterval(call, delay);
17297     };
17298
17299     /**
17300      * Cancel the last queued timeout
17301      */
17302     this.cancel = function(){
17303         if (id) {
17304             clearInterval(id);
17305             id = null;
17306         }
17307     };
17308 };
17309 Ext.require('Ext.util.DelayedTask', function() {
17310
17311     Ext.util.Event = Ext.extend(Object, (function() {
17312         function createBuffered(handler, listener, o, scope) {
17313             listener.task = new Ext.util.DelayedTask();
17314             return function() {
17315                 listener.task.delay(o.buffer, handler, scope, Ext.Array.toArray(arguments));
17316             };
17317         }
17318
17319         function createDelayed(handler, listener, o, scope) {
17320             return function() {
17321                 var task = new Ext.util.DelayedTask();
17322                 if (!listener.tasks) {
17323                     listener.tasks = [];
17324                 }
17325                 listener.tasks.push(task);
17326                 task.delay(o.delay || 10, handler, scope, Ext.Array.toArray(arguments));
17327             };
17328         }
17329
17330         function createSingle(handler, listener, o, scope) {
17331             return function() {
17332                 listener.ev.removeListener(listener.fn, scope);
17333                 return handler.apply(scope, arguments);
17334             };
17335         }
17336
17337         return {
17338             isEvent: true,
17339
17340             constructor: function(observable, name) {
17341                 this.name = name;
17342                 this.observable = observable;
17343                 this.listeners = [];
17344             },
17345
17346             addListener: function(fn, scope, options) {
17347                 var me = this,
17348                     listener;
17349                     scope = scope || me.observable;
17350
17351                 if (!fn) {
17352                     Ext.Error.raise({
17353                         sourceClass: Ext.getClassName(this.observable),
17354                         sourceMethod: "addListener",
17355                         msg: "The specified callback function is undefined"
17356                     });
17357                 }
17358
17359                 if (!me.isListening(fn, scope)) {
17360                     listener = me.createListener(fn, scope, options);
17361                     if (me.firing) {
17362                         // if we are currently firing this event, don't disturb the listener loop
17363                         me.listeners = me.listeners.slice(0);
17364                     }
17365                     me.listeners.push(listener);
17366                 }
17367             },
17368
17369             createListener: function(fn, scope, o) {
17370                 o = o || {};
17371                 scope = scope || this.observable;
17372
17373                 var listener = {
17374                         fn: fn,
17375                         scope: scope,
17376                         o: o,
17377                         ev: this
17378                     },
17379                     handler = fn;
17380
17381                 // The order is important. The 'single' wrapper must be wrapped by the 'buffer' and 'delayed' wrapper
17382                 // because the event removal that the single listener does destroys the listener's DelayedTask(s)
17383                 if (o.single) {
17384                     handler = createSingle(handler, listener, o, scope);
17385                 }
17386                 if (o.delay) {
17387                     handler = createDelayed(handler, listener, o, scope);
17388                 }
17389                 if (o.buffer) {
17390                     handler = createBuffered(handler, listener, o, scope);
17391                 }
17392
17393                 listener.fireFn = handler;
17394                 return listener;
17395             },
17396
17397             findListener: function(fn, scope) {
17398                 var listeners = this.listeners,
17399                 i = listeners.length,
17400                 listener,
17401                 s;
17402
17403                 while (i--) {
17404                     listener = listeners[i];
17405                     if (listener) {
17406                         s = listener.scope;
17407                         if (listener.fn == fn && (s == scope || s == this.observable)) {
17408                             return i;
17409                         }
17410                     }
17411                 }
17412
17413                 return - 1;
17414             },
17415
17416             isListening: function(fn, scope) {
17417                 return this.findListener(fn, scope) !== -1;
17418             },
17419
17420             removeListener: function(fn, scope) {
17421                 var me = this,
17422                     index,
17423                     listener,
17424                     k;
17425                 index = me.findListener(fn, scope);
17426                 if (index != -1) {
17427                     listener = me.listeners[index];
17428
17429                     if (me.firing) {
17430                         me.listeners = me.listeners.slice(0);
17431                     }
17432
17433                     // cancel and remove a buffered handler that hasn't fired yet
17434                     if (listener.task) {
17435                         listener.task.cancel();
17436                         delete listener.task;
17437                     }
17438
17439                     // cancel and remove all delayed handlers that haven't fired yet
17440                     k = listener.tasks && listener.tasks.length;
17441                     if (k) {
17442                         while (k--) {
17443                             listener.tasks[k].cancel();
17444                         }
17445                         delete listener.tasks;
17446                     }
17447
17448                     // remove this listener from the listeners array
17449                     Ext.Array.erase(me.listeners, index, 1);
17450                     return true;
17451                 }
17452
17453                 return false;
17454             },
17455
17456             // Iterate to stop any buffered/delayed events
17457             clearListeners: function() {
17458                 var listeners = this.listeners,
17459                     i = listeners.length;
17460
17461                 while (i--) {
17462                     this.removeListener(listeners[i].fn, listeners[i].scope);
17463                 }
17464             },
17465
17466             fire: function() {
17467                 var me = this,
17468                     listeners = me.listeners,
17469                     count = listeners.length,
17470                     i,
17471                     args,
17472                     listener;
17473
17474                 if (count > 0) {
17475                     me.firing = true;
17476                     for (i = 0; i < count; i++) {
17477                         listener = listeners[i];
17478                         args = arguments.length ? Array.prototype.slice.call(arguments, 0) : [];
17479                         if (listener.o) {
17480                             args.push(listener.o);
17481                         }
17482                         if (listener && listener.fireFn.apply(listener.scope || me.observable, args) === false) {
17483                             return (me.firing = false);
17484                         }
17485                     }
17486                 }
17487                 me.firing = false;
17488                 return true;
17489             }
17490         };
17491     })());
17492 });
17493
17494 /**
17495  * @class Ext.EventManager
17496  * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
17497  * several useful events directly.
17498  * See {@link Ext.EventObject} for more details on normalized event objects.
17499  * @singleton
17500  */
17501 Ext.EventManager = {
17502
17503     // --------------------- onReady ---------------------
17504
17505     /**
17506      * Check if we have bound our global onReady listener
17507      * @private
17508      */
17509     hasBoundOnReady: false,
17510
17511     /**
17512      * Check if fireDocReady has been called
17513      * @private
17514      */
17515     hasFiredReady: false,
17516
17517     /**
17518      * Timer for the document ready event in old IE versions
17519      * @private
17520      */
17521     readyTimeout: null,
17522
17523     /**
17524      * Checks if we have bound an onreadystatechange event
17525      * @private
17526      */
17527     hasOnReadyStateChange: false,
17528
17529     /**
17530      * Holds references to any onReady functions
17531      * @private
17532      */
17533     readyEvent: new Ext.util.Event(),
17534
17535     /**
17536      * Check the ready state for old IE versions
17537      * @private
17538      * @return {Boolean} True if the document is ready
17539      */
17540     checkReadyState: function(){
17541         var me = Ext.EventManager;
17542
17543         if(window.attachEvent){
17544             // See here for reference: http://javascript.nwbox.com/IEContentLoaded/
17545             // licensed courtesy of http://developer.yahoo.com/yui/license.html
17546             if (window != top) {
17547                 return false;
17548             }
17549             try{
17550                 document.documentElement.doScroll('left');
17551             }catch(e){
17552                 return false;
17553             }
17554             me.fireDocReady();
17555             return true;
17556         }
17557         if (document.readyState == 'complete') {
17558             me.fireDocReady();
17559             return true;
17560         }
17561         me.readyTimeout = setTimeout(arguments.callee, 2);
17562         return false;
17563     },
17564
17565     /**
17566      * Binds the appropriate browser event for checking if the DOM has loaded.
17567      * @private
17568      */
17569     bindReadyEvent: function(){
17570         var me = Ext.EventManager;
17571         if (me.hasBoundOnReady) {
17572             return;
17573         }
17574
17575         if (document.addEventListener) {
17576             document.addEventListener('DOMContentLoaded', me.fireDocReady, false);
17577             // fallback, load will ~always~ fire
17578             window.addEventListener('load', me.fireDocReady, false);
17579         } else {
17580             // check if the document is ready, this will also kick off the scroll checking timer
17581             if (!me.checkReadyState()) {
17582                 document.attachEvent('onreadystatechange', me.checkReadyState);
17583                 me.hasOnReadyStateChange = true;
17584             }
17585             // fallback, onload will ~always~ fire
17586             window.attachEvent('onload', me.fireDocReady, false);
17587         }
17588         me.hasBoundOnReady = true;
17589     },
17590
17591     /**
17592      * We know the document is loaded, so trigger any onReady events.
17593      * @private
17594      */
17595     fireDocReady: function(){
17596         var me = Ext.EventManager;
17597
17598         // only unbind these events once
17599         if (!me.hasFiredReady) {
17600             me.hasFiredReady = true;
17601
17602             if (document.addEventListener) {
17603                 document.removeEventListener('DOMContentLoaded', me.fireDocReady, false);
17604                 window.removeEventListener('load', me.fireDocReady, false);
17605             } else {
17606                 if (me.readyTimeout !== null) {
17607                     clearTimeout(me.readyTimeout);
17608                 }
17609                 if (me.hasOnReadyStateChange) {
17610                     document.detachEvent('onreadystatechange', me.checkReadyState);
17611                 }
17612                 window.detachEvent('onload', me.fireDocReady);
17613             }
17614             Ext.supports.init();
17615         }
17616         if (!Ext.isReady) {
17617             Ext.isReady = true;
17618             me.onWindowUnload();
17619             me.readyEvent.fire();
17620         }
17621     },
17622
17623     /**
17624      * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
17625      * accessed shorthanded as Ext.onReady().
17626      * @param {Function} fn The method the event invokes.
17627      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
17628      * @param {Boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}.
17629      */
17630     onDocumentReady: function(fn, scope, options){
17631         options = options || {};
17632         var me = Ext.EventManager,
17633             readyEvent = me.readyEvent;
17634
17635         // force single to be true so our event is only ever fired once.
17636         options.single = true;
17637
17638         // Document already loaded, let's just fire it
17639         if (Ext.isReady) {
17640             readyEvent.addListener(fn, scope, options);
17641             readyEvent.fire();
17642         } else {
17643             options.delay = options.delay || 1;
17644             readyEvent.addListener(fn, scope, options);
17645             me.bindReadyEvent();
17646         }
17647     },
17648
17649
17650     // --------------------- event binding ---------------------
17651
17652     /**
17653      * Contains a list of all document mouse downs, so we can ensure they fire even when stopEvent is called.
17654      * @private
17655      */
17656     stoppedMouseDownEvent: new Ext.util.Event(),
17657
17658     /**
17659      * Options to parse for the 4th argument to addListener.
17660      * @private
17661      */
17662     propRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|freezeEvent)$/,
17663
17664     /**
17665      * Get the id of the element. If one has not been assigned, automatically assign it.
17666      * @param {HTMLElement/Ext.Element} element The element to get the id for.
17667      * @return {String} id
17668      */
17669     getId : function(element) {
17670         var skipGarbageCollection = false,
17671             id;
17672
17673         element = Ext.getDom(element);
17674
17675         if (element === document || element === window) {
17676             id = element === document ? Ext.documentId : Ext.windowId;
17677         }
17678         else {
17679             id = Ext.id(element);
17680         }
17681         // skip garbage collection for special elements (window, document, iframes)
17682         if (element && (element.getElementById || element.navigator)) {
17683             skipGarbageCollection = true;
17684         }
17685
17686         if (!Ext.cache[id]){
17687             Ext.Element.addToCache(new Ext.Element(element), id);
17688             if (skipGarbageCollection) {
17689                 Ext.cache[id].skipGarbageCollection = true;
17690             }
17691         }
17692         return id;
17693     },
17694
17695     /**
17696      * Convert a "config style" listener into a set of flat arguments so they can be passed to addListener
17697      * @private
17698      * @param {Object} element The element the event is for
17699      * @param {Object} event The event configuration
17700      * @param {Object} isRemove True if a removal should be performed, otherwise an add will be done.
17701      */
17702     prepareListenerConfig: function(element, config, isRemove){
17703         var me = this,
17704             propRe = me.propRe,
17705             key, value, args;
17706
17707         // loop over all the keys in the object
17708         for (key in config) {
17709             if (config.hasOwnProperty(key)) {
17710                 // if the key is something else then an event option
17711                 if (!propRe.test(key)) {
17712                     value = config[key];
17713                     // if the value is a function it must be something like click: function(){}, scope: this
17714                     // which means that there might be multiple event listeners with shared options
17715                     if (Ext.isFunction(value)) {
17716                         // shared options
17717                         args = [element, key, value, config.scope, config];
17718                     } else {
17719                         // if its not a function, it must be an object like click: {fn: function(){}, scope: this}
17720                         args = [element, key, value.fn, value.scope, value];
17721                     }
17722
17723                     if (isRemove === true) {
17724                         me.removeListener.apply(this, args);
17725                     } else {
17726                         me.addListener.apply(me, args);
17727                     }
17728                 }
17729             }
17730         }
17731     },
17732
17733     /**
17734      * Normalize cross browser event differences
17735      * @private
17736      * @param {Object} eventName The event name
17737      * @param {Object} fn The function to execute
17738      * @return {Object} The new event name/function
17739      */
17740     normalizeEvent: function(eventName, fn){
17741         if (/mouseenter|mouseleave/.test(eventName) && !Ext.supports.MouseEnterLeave) {
17742             if (fn) {
17743                 fn = Ext.Function.createInterceptor(fn, this.contains, this);
17744             }
17745             eventName = eventName == 'mouseenter' ? 'mouseover' : 'mouseout';
17746         } else if (eventName == 'mousewheel' && !Ext.supports.MouseWheel && !Ext.isOpera){
17747             eventName = 'DOMMouseScroll';
17748         }
17749         return {
17750             eventName: eventName,
17751             fn: fn
17752         };
17753     },
17754
17755     /**
17756      * Checks whether the event's relatedTarget is contained inside (or <b>is</b>) the element.
17757      * @private
17758      * @param {Object} event
17759      */
17760     contains: function(event){
17761         var parent = event.browserEvent.currentTarget,
17762             child = this.getRelatedTarget(event);
17763
17764         if (parent && parent.firstChild) {
17765             while (child) {
17766                 if (child === parent) {
17767                     return false;
17768                 }
17769                 child = child.parentNode;
17770                 if (child && (child.nodeType != 1)) {
17771                     child = null;
17772                 }
17773             }
17774         }
17775         return true;
17776     },
17777
17778     /**
17779     * Appends an event handler to an element.  The shorthand version {@link #on} is equivalent.  Typically you will
17780     * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version.
17781     * @param {String/HTMLElement} el The html element or id to assign the event handler to.
17782     * @param {String} eventName The name of the event to listen for.
17783     * @param {Function} handler The handler function the event invokes. This function is passed
17784     * the following parameters:<ul>
17785     * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
17786     * <li>t : Element<div class="sub-desc">The {@link Ext.Element Element} which was the target of the event.
17787     * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
17788     * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
17789     * </ul>
17790     * @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>.
17791     * @param {Object} options (optional) An object containing handler configuration properties.
17792     * This may contain any of the following properties:<ul>
17793     * <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>
17794     * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
17795     * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
17796     * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
17797     * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
17798     * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
17799     * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
17800     * <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>
17801     * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
17802     * by the specified number of milliseconds. If the event fires again within that time, the original
17803     * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
17804     * <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>
17805     * </ul><br>
17806     * <p>See {@link Ext.Element#addListener} for examples of how to use these options.</p>
17807     */
17808     addListener: function(element, eventName, fn, scope, options){
17809         // Check if we've been passed a "config style" event.
17810         if (typeof eventName !== 'string') {
17811             this.prepareListenerConfig(element, eventName);
17812             return;
17813         }
17814
17815         var dom = Ext.getDom(element),
17816             bind,
17817             wrap;
17818
17819         if (!dom){
17820             Ext.Error.raise({
17821                 sourceClass: 'Ext.EventManager',
17822                 sourceMethod: 'addListener',
17823                 targetElement: element,
17824                 eventName: eventName,
17825                 msg: 'Error adding "' + eventName + '\" listener for nonexistent element "' + element + '"'
17826             });
17827         }
17828         if (!fn) {
17829             Ext.Error.raise({
17830                 sourceClass: 'Ext.EventManager',
17831                 sourceMethod: 'addListener',
17832                 targetElement: element,
17833                 eventName: eventName,
17834                 msg: 'Error adding "' + eventName + '\" listener. The handler function is undefined.'
17835             });
17836         }
17837
17838         // create the wrapper function
17839         options = options || {};
17840
17841         bind = this.normalizeEvent(eventName, fn);
17842         wrap = this.createListenerWrap(dom, eventName, bind.fn, scope, options);
17843
17844
17845         if (dom.attachEvent) {
17846             dom.attachEvent('on' + bind.eventName, wrap);
17847         } else {
17848             dom.addEventListener(bind.eventName, wrap, options.capture || false);
17849         }
17850
17851         if (dom == document && eventName == 'mousedown') {
17852             this.stoppedMouseDownEvent.addListener(wrap);
17853         }
17854
17855         // add all required data into the event cache
17856         this.getEventListenerCache(dom, eventName).push({
17857             fn: fn,
17858             wrap: wrap,
17859             scope: scope
17860         });
17861     },
17862
17863     /**
17864     * Removes an event handler from an element.  The shorthand version {@link #un} is equivalent.  Typically
17865     * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version.
17866     * @param {String/HTMLElement} el The id or html element from which to remove the listener.
17867     * @param {String} eventName The name of the event.
17868     * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
17869     * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
17870     * then this must refer to the same object.
17871     */
17872     removeListener : function(element, eventName, fn, scope) {
17873         // handle our listener config object syntax
17874         if (typeof eventName !== 'string') {
17875             this.prepareListenerConfig(element, eventName, true);
17876             return;
17877         }
17878
17879         var dom = Ext.getDom(element),
17880             cache = this.getEventListenerCache(dom, eventName),
17881             bindName = this.normalizeEvent(eventName).eventName,
17882             i = cache.length, j,
17883             listener, wrap, tasks;
17884
17885
17886         while (i--) {
17887             listener = cache[i];
17888
17889             if (listener && (!fn || listener.fn == fn) && (!scope || listener.scope === scope)) {
17890                 wrap = listener.wrap;
17891
17892                 // clear buffered calls
17893                 if (wrap.task) {
17894                     clearTimeout(wrap.task);
17895                     delete wrap.task;
17896                 }
17897
17898                 // clear delayed calls
17899                 j = wrap.tasks && wrap.tasks.length;
17900                 if (j) {
17901                     while (j--) {
17902                         clearTimeout(wrap.tasks[j]);
17903                     }
17904                     delete wrap.tasks;
17905                 }
17906
17907                 if (dom.detachEvent) {
17908                     dom.detachEvent('on' + bindName, wrap);
17909                 } else {
17910                     dom.removeEventListener(bindName, wrap, false);
17911                 }
17912
17913                 if (wrap && dom == document && eventName == 'mousedown') {
17914                     this.stoppedMouseDownEvent.removeListener(wrap);
17915                 }
17916
17917                 // remove listener from cache
17918                 Ext.Array.erase(cache, i, 1);
17919             }
17920         }
17921     },
17922
17923     /**
17924     * Removes all event handers from an element.  Typically you will use {@link Ext.Element#removeAllListeners}
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     */
17928     removeAll : function(element){
17929         var dom = Ext.getDom(element),
17930             cache, ev;
17931         if (!dom) {
17932             return;
17933         }
17934         cache = this.getElementEventCache(dom);
17935
17936         for (ev in cache) {
17937             if (cache.hasOwnProperty(ev)) {
17938                 this.removeListener(dom, ev);
17939             }
17940         }
17941         Ext.cache[dom.id].events = {};
17942     },
17943
17944     /**
17945      * Recursively removes all previous added listeners from an element and its children. Typically you will use {@link Ext.Element#purgeAllListeners}
17946      * directly on an Element in favor of calling this version.
17947      * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
17948      * @param {String} eventName (optional) The name of the event.
17949      */
17950     purgeElement : function(element, eventName) {
17951         var dom = Ext.getDom(element),
17952             i = 0, len;
17953
17954         if(eventName) {
17955             this.removeListener(dom, eventName);
17956         }
17957         else {
17958             this.removeAll(dom);
17959         }
17960
17961         if(dom && dom.childNodes) {
17962             for(len = element.childNodes.length; i < len; i++) {
17963                 this.purgeElement(element.childNodes[i], eventName);
17964             }
17965         }
17966     },
17967
17968     /**
17969      * Create the wrapper function for the event
17970      * @private
17971      * @param {HTMLElement} dom The dom element
17972      * @param {String} ename The event name
17973      * @param {Function} fn The function to execute
17974      * @param {Object} scope The scope to execute callback in
17975      * @param {Object} options The options
17976      * @return {Function} the wrapper function
17977      */
17978     createListenerWrap : function(dom, ename, fn, scope, options) {
17979         options = options || {};
17980
17981         var f, gen;
17982
17983         return function wrap(e, args) {
17984             // Compile the implementation upon first firing
17985             if (!gen) {
17986                 f = ['if(!Ext) {return;}'];
17987
17988                 if(options.buffer || options.delay || options.freezeEvent) {
17989                     f.push('e = new Ext.EventObjectImpl(e, ' + (options.freezeEvent ? 'true' : 'false' ) + ');');
17990                 } else {
17991                     f.push('e = Ext.EventObject.setEvent(e);');
17992                 }
17993
17994                 if (options.delegate) {
17995                     f.push('var t = e.getTarget("' + options.delegate + '", this);');
17996                     f.push('if(!t) {return;}');
17997                 } else {
17998                     f.push('var t = e.target;');
17999                 }
18000
18001                 if (options.target) {
18002                     f.push('if(e.target !== options.target) {return;}');
18003                 }
18004
18005                 if(options.stopEvent) {
18006                     f.push('e.stopEvent();');
18007                 } else {
18008                     if(options.preventDefault) {
18009                         f.push('e.preventDefault();');
18010                     }
18011                     if(options.stopPropagation) {
18012                         f.push('e.stopPropagation();');
18013                     }
18014                 }
18015
18016                 if(options.normalized === false) {
18017                     f.push('e = e.browserEvent;');
18018                 }
18019
18020                 if(options.buffer) {
18021                     f.push('(wrap.task && clearTimeout(wrap.task));');
18022                     f.push('wrap.task = setTimeout(function(){');
18023                 }
18024
18025                 if(options.delay) {
18026                     f.push('wrap.tasks = wrap.tasks || [];');
18027                     f.push('wrap.tasks.push(setTimeout(function(){');
18028                 }
18029
18030                 // finally call the actual handler fn
18031                 f.push('fn.call(scope || dom, e, t, options);');
18032
18033                 if(options.single) {
18034                     f.push('Ext.EventManager.removeListener(dom, ename, fn, scope);');
18035                 }
18036
18037                 if(options.delay) {
18038                     f.push('}, ' + options.delay + '));');
18039                 }
18040
18041                 if(options.buffer) {
18042                     f.push('}, ' + options.buffer + ');');
18043                 }
18044
18045                 gen = Ext.functionFactory('e', 'options', 'fn', 'scope', 'ename', 'dom', 'wrap', 'args', f.join('\n'));
18046             }
18047
18048             gen.call(dom, e, options, fn, scope, ename, dom, wrap, args);
18049         };
18050     },
18051
18052     /**
18053      * Get the event cache for a particular element for a particular event
18054      * @private
18055      * @param {HTMLElement} element The element
18056      * @param {Object} eventName The event name
18057      * @return {Array} The events for the element
18058      */
18059     getEventListenerCache : function(element, eventName) {
18060         if (!element) {
18061             return [];
18062         }
18063
18064         var eventCache = this.getElementEventCache(element);
18065         return eventCache[eventName] || (eventCache[eventName] = []);
18066     },
18067
18068     /**
18069      * Gets the event cache for the object
18070      * @private
18071      * @param {HTMLElement} element The element
18072      * @return {Object} The event cache for the object
18073      */
18074     getElementEventCache : function(element) {
18075         if (!element) {
18076             return {};
18077         }
18078         var elementCache = Ext.cache[this.getId(element)];
18079         return elementCache.events || (elementCache.events = {});
18080     },
18081
18082     // --------------------- utility methods ---------------------
18083     mouseLeaveRe: /(mouseout|mouseleave)/,
18084     mouseEnterRe: /(mouseover|mouseenter)/,
18085
18086     /**
18087      * Stop the event (preventDefault and stopPropagation)
18088      * @param {Event} The event to stop
18089      */
18090     stopEvent: function(event) {
18091         this.stopPropagation(event);
18092         this.preventDefault(event);
18093     },
18094
18095     /**
18096      * Cancels bubbling of the event.
18097      * @param {Event} The event to stop bubbling.
18098      */
18099     stopPropagation: function(event) {
18100         event = event.browserEvent || event;
18101         if (event.stopPropagation) {
18102             event.stopPropagation();
18103         } else {
18104             event.cancelBubble = true;
18105         }
18106     },
18107
18108     /**
18109      * Prevents the browsers default handling of the event.
18110      * @param {Event} The event to prevent the default
18111      */
18112     preventDefault: function(event) {
18113         event = event.browserEvent || event;
18114         if (event.preventDefault) {
18115             event.preventDefault();
18116         } else {
18117             event.returnValue = false;
18118             // Some keys events require setting the keyCode to -1 to be prevented
18119             try {
18120               // all ctrl + X and F1 -> F12
18121               if (event.ctrlKey || event.keyCode > 111 && event.keyCode < 124) {
18122                   event.keyCode = -1;
18123               }
18124             } catch (e) {
18125                 // see this outdated document http://support.microsoft.com/kb/934364/en-us for more info
18126             }
18127         }
18128     },
18129
18130     /**
18131      * Gets the related target from the event.
18132      * @param {Object} event The event
18133      * @return {HTMLElement} The related target.
18134      */
18135     getRelatedTarget: function(event) {
18136         event = event.browserEvent || event;
18137         var target = event.relatedTarget;
18138         if (!target) {
18139             if (this.mouseLeaveRe.test(event.type)) {
18140                 target = event.toElement;
18141             } else if (this.mouseEnterRe.test(event.type)) {
18142                 target = event.fromElement;
18143             }
18144         }
18145         return this.resolveTextNode(target);
18146     },
18147
18148     /**
18149      * Gets the x coordinate from the event
18150      * @param {Object} event The event
18151      * @return {Number} The x coordinate
18152      */
18153     getPageX: function(event) {
18154         return this.getXY(event)[0];
18155     },
18156
18157     /**
18158      * Gets the y coordinate from the event
18159      * @param {Object} event The event
18160      * @return {Number} The y coordinate
18161      */
18162     getPageY: function(event) {
18163         return this.getXY(event)[1];
18164     },
18165
18166     /**
18167      * Gets the x & y coordinate from the event
18168      * @param {Object} event The event
18169      * @return {Number[]} The x/y coordinate
18170      */
18171     getPageXY: function(event) {
18172         event = event.browserEvent || event;
18173         var x = event.pageX,
18174             y = event.pageY,
18175             doc = document.documentElement,
18176             body = document.body;
18177
18178         // pageX/pageY not available (undefined, not null), use clientX/clientY instead
18179         if (!x && x !== 0) {
18180             x = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
18181             y = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
18182         }
18183         return [x, y];
18184     },
18185
18186     /**
18187      * Gets the target of the event.
18188      * @param {Object} event The event
18189      * @return {HTMLElement} target
18190      */
18191     getTarget: function(event) {
18192         event = event.browserEvent || event;
18193         return this.resolveTextNode(event.target || event.srcElement);
18194     },
18195
18196     /**
18197      * Resolve any text nodes accounting for browser differences.
18198      * @private
18199      * @param {HTMLElement} node The node
18200      * @return {HTMLElement} The resolved node
18201      */
18202     // 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.
18203     resolveTextNode: Ext.isGecko ?
18204         function(node) {
18205             if (!node) {
18206                 return;
18207             }
18208             // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197
18209             var s = HTMLElement.prototype.toString.call(node);
18210             if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') {
18211                 return;
18212             }
18213                 return node.nodeType == 3 ? node.parentNode: node;
18214             }: function(node) {
18215                 return node && node.nodeType == 3 ? node.parentNode: node;
18216             },
18217
18218     // --------------------- custom event binding ---------------------
18219
18220     // Keep track of the current width/height
18221     curWidth: 0,
18222     curHeight: 0,
18223
18224     /**
18225      * Adds a listener to be notified when the browser window is resized and provides resize event buffering (100 milliseconds),
18226      * passes new viewport width and height to handlers.
18227      * @param {Function} fn      The handler function the window resize event invokes.
18228      * @param {Object}   scope   The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
18229      * @param {Boolean}  options Options object as passed to {@link Ext.Element#addListener}
18230      */
18231     onWindowResize: function(fn, scope, options){
18232         var resize = this.resizeEvent;
18233         if(!resize){
18234             this.resizeEvent = resize = new Ext.util.Event();
18235             this.on(window, 'resize', this.fireResize, this, {buffer: 100});
18236         }
18237         resize.addListener(fn, scope, options);
18238     },
18239
18240     /**
18241      * Fire the resize event.
18242      * @private
18243      */
18244     fireResize: function(){
18245         var me = this,
18246             w = Ext.Element.getViewWidth(),
18247             h = Ext.Element.getViewHeight();
18248
18249          //whacky problem in IE where the resize event will sometimes fire even though the w/h are the same.
18250          if(me.curHeight != h || me.curWidth != w){
18251              me.curHeight = h;
18252              me.curWidth = w;
18253              me.resizeEvent.fire(w, h);
18254          }
18255     },
18256
18257     /**
18258      * Removes the passed window resize listener.
18259      * @param {Function} fn        The method the event invokes
18260      * @param {Object}   scope    The scope of handler
18261      */
18262     removeResizeListener: function(fn, scope){
18263         if (this.resizeEvent) {
18264             this.resizeEvent.removeListener(fn, scope);
18265         }
18266     },
18267
18268     onWindowUnload: function() {
18269         var unload = this.unloadEvent;
18270         if (!unload) {
18271             this.unloadEvent = unload = new Ext.util.Event();
18272             this.addListener(window, 'unload', this.fireUnload, this);
18273         }
18274     },
18275
18276     /**
18277      * Fires the unload event for items bound with onWindowUnload
18278      * @private
18279      */
18280     fireUnload: function() {
18281         // wrap in a try catch, could have some problems during unload
18282         try {
18283             this.removeUnloadListener();
18284             // Work around FF3 remembering the last scroll position when refreshing the grid and then losing grid view
18285             if (Ext.isGecko3) {
18286                 var gridviews = Ext.ComponentQuery.query('gridview'),
18287                     i = 0,
18288                     ln = gridviews.length;
18289                 for (; i < ln; i++) {
18290                     gridviews[i].scrollToTop();
18291                 }
18292             }
18293             // Purge all elements in the cache
18294             var el,
18295                 cache = Ext.cache;
18296             for (el in cache) {
18297                 if (cache.hasOwnProperty(el)) {
18298                     Ext.EventManager.removeAll(el);
18299                 }
18300             }
18301         } catch(e) {
18302         }
18303     },
18304
18305     /**
18306      * Removes the passed window unload listener.
18307      * @param {Function} fn        The method the event invokes
18308      * @param {Object}   scope    The scope of handler
18309      */
18310     removeUnloadListener: function(){
18311         if (this.unloadEvent) {
18312             this.removeListener(window, 'unload', this.fireUnload);
18313         }
18314     },
18315
18316     /**
18317      * note 1: IE fires ONLY the keydown event on specialkey autorepeat
18318      * note 2: Safari < 3.1, Gecko (Mac/Linux) & Opera fire only the keypress event on specialkey autorepeat
18319      * (research done by Jan Wolter at http://unixpapa.com/js/key.html)
18320      * @private
18321      */
18322     useKeyDown: Ext.isWebKit ?
18323                    parseInt(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1], 10) >= 525 :
18324                    !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera),
18325
18326     /**
18327      * Indicates which event to use for getting key presses.
18328      * @return {String} The appropriate event name.
18329      */
18330     getKeyEvent: function(){
18331         return this.useKeyDown ? 'keydown' : 'keypress';
18332     }
18333 };
18334
18335 /**
18336  * Alias for {@link Ext.Loader#onReady Ext.Loader.onReady} with withDomReady set to true
18337  * @member Ext
18338  * @method onReady
18339  */
18340 Ext.onReady = function(fn, scope, options) {
18341     Ext.Loader.onReady(fn, scope, true, options);
18342 };
18343
18344 /**
18345  * Alias for {@link Ext.EventManager#onDocumentReady Ext.EventManager.onDocumentReady}
18346  * @member Ext
18347  * @method onDocumentReady
18348  */
18349 Ext.onDocumentReady = Ext.EventManager.onDocumentReady;
18350
18351 /**
18352  * Alias for {@link Ext.EventManager#addListener Ext.EventManager.addListener}
18353  * @member Ext.EventManager
18354  * @method on
18355  */
18356 Ext.EventManager.on = Ext.EventManager.addListener;
18357
18358 /**
18359  * Alias for {@link Ext.EventManager#removeListener Ext.EventManager.removeListener}
18360  * @member Ext.EventManager
18361  * @method un
18362  */
18363 Ext.EventManager.un = Ext.EventManager.removeListener;
18364
18365 (function(){
18366     var initExtCss = function() {
18367         // find the body element
18368         var bd = document.body || document.getElementsByTagName('body')[0],
18369             baseCSSPrefix = Ext.baseCSSPrefix,
18370             cls = [baseCSSPrefix + 'body'],
18371             htmlCls = [],
18372             html;
18373
18374         if (!bd) {
18375             return false;
18376         }
18377
18378         html = bd.parentNode;
18379
18380         function add (c) {
18381             cls.push(baseCSSPrefix + c);
18382         }
18383
18384         //Let's keep this human readable!
18385         if (Ext.isIE) {
18386             add('ie');
18387
18388             // very often CSS needs to do checks like "IE7+" or "IE6 or 7". To help
18389             // reduce the clutter (since CSS/SCSS cannot do these tests), we add some
18390             // additional classes:
18391             //
18392             //      x-ie7p      : IE7+      :  7 <= ieVer
18393             //      x-ie7m      : IE7-      :  ieVer <= 7
18394             //      x-ie8p      : IE8+      :  8 <= ieVer
18395             //      x-ie8m      : IE8-      :  ieVer <= 8
18396             //      x-ie9p      : IE9+      :  9 <= ieVer
18397             //      x-ie78      : IE7 or 8  :  7 <= ieVer <= 8
18398             //
18399             if (Ext.isIE6) {
18400                 add('ie6');
18401             } else { // ignore pre-IE6 :)
18402                 add('ie7p');
18403
18404                 if (Ext.isIE7) {
18405                     add('ie7');
18406                 } else {
18407                     add('ie8p');
18408
18409                     if (Ext.isIE8) {
18410                         add('ie8');
18411                     } else {
18412                         add('ie9p');
18413
18414                         if (Ext.isIE9) {
18415                             add('ie9');
18416                         }
18417                     }
18418                 }
18419             }
18420
18421             if (Ext.isIE6 || Ext.isIE7) {
18422                 add('ie7m');
18423             }
18424             if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
18425                 add('ie8m');
18426             }
18427             if (Ext.isIE7 || Ext.isIE8) {
18428                 add('ie78');
18429             }
18430         }
18431         if (Ext.isGecko) {
18432             add('gecko');
18433             if (Ext.isGecko3) {
18434                 add('gecko3');
18435             }
18436             if (Ext.isGecko4) {
18437                 add('gecko4');
18438             }
18439             if (Ext.isGecko5) {
18440                 add('gecko5');
18441             }
18442         }
18443         if (Ext.isOpera) {
18444             add('opera');
18445         }
18446         if (Ext.isWebKit) {
18447             add('webkit');
18448         }
18449         if (Ext.isSafari) {
18450             add('safari');
18451             if (Ext.isSafari2) {
18452                 add('safari2');
18453             }
18454             if (Ext.isSafari3) {
18455                 add('safari3');
18456             }
18457             if (Ext.isSafari4) {
18458                 add('safari4');
18459             }
18460             if (Ext.isSafari5) {
18461                 add('safari5');
18462             }
18463         }
18464         if (Ext.isChrome) {
18465             add('chrome');
18466         }
18467         if (Ext.isMac) {
18468             add('mac');
18469         }
18470         if (Ext.isLinux) {
18471             add('linux');
18472         }
18473         if (!Ext.supports.CSS3BorderRadius) {
18474             add('nbr');
18475         }
18476         if (!Ext.supports.CSS3LinearGradient) {
18477             add('nlg');
18478         }
18479         if (!Ext.scopeResetCSS) {
18480             add('reset');
18481         }
18482
18483         // add to the parent to allow for selectors x-strict x-border-box, also set the isBorderBox property correctly
18484         if (html) {
18485             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
18486                 Ext.isBorderBox = false;
18487             }
18488             else {
18489                 Ext.isBorderBox = true;
18490             }
18491
18492             htmlCls.push(baseCSSPrefix + (Ext.isBorderBox ? 'border-box' : 'strict'));
18493             if (!Ext.isStrict) {
18494                 htmlCls.push(baseCSSPrefix + 'quirks');
18495             }
18496             Ext.fly(html, '_internal').addCls(htmlCls);
18497         }
18498
18499         Ext.fly(bd, '_internal').addCls(cls);
18500         return true;
18501     };
18502
18503     Ext.onReady(initExtCss);
18504 })();
18505
18506 /**
18507  * @class Ext.EventObject
18508
18509 Just as {@link Ext.Element} wraps around a native DOM node, Ext.EventObject
18510 wraps the browser's native event-object normalizing cross-browser differences,
18511 such as which mouse button is clicked, keys pressed, mechanisms to stop
18512 event-propagation along with a method to prevent default actions from taking place.
18513
18514 For example:
18515
18516     function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
18517         e.preventDefault();
18518         var target = e.getTarget(); // same as t (the target HTMLElement)
18519         ...
18520     }
18521
18522     var myDiv = {@link Ext#get Ext.get}("myDiv");  // get reference to an {@link Ext.Element}
18523     myDiv.on(         // 'on' is shorthand for addListener
18524         "click",      // perform an action on click of myDiv
18525         handleClick   // reference to the action handler
18526     );
18527
18528     // other methods to do the same:
18529     Ext.EventManager.on("myDiv", 'click', handleClick);
18530     Ext.EventManager.addListener("myDiv", 'click', handleClick);
18531
18532  * @singleton
18533  * @markdown
18534  */
18535 Ext.define('Ext.EventObjectImpl', {
18536     uses: ['Ext.util.Point'],
18537
18538     /** Key constant @type Number */
18539     BACKSPACE: 8,
18540     /** Key constant @type Number */
18541     TAB: 9,
18542     /** Key constant @type Number */
18543     NUM_CENTER: 12,
18544     /** Key constant @type Number */
18545     ENTER: 13,
18546     /** Key constant @type Number */
18547     RETURN: 13,
18548     /** Key constant @type Number */
18549     SHIFT: 16,
18550     /** Key constant @type Number */
18551     CTRL: 17,
18552     /** Key constant @type Number */
18553     ALT: 18,
18554     /** Key constant @type Number */
18555     PAUSE: 19,
18556     /** Key constant @type Number */
18557     CAPS_LOCK: 20,
18558     /** Key constant @type Number */
18559     ESC: 27,
18560     /** Key constant @type Number */
18561     SPACE: 32,
18562     /** Key constant @type Number */
18563     PAGE_UP: 33,
18564     /** Key constant @type Number */
18565     PAGE_DOWN: 34,
18566     /** Key constant @type Number */
18567     END: 35,
18568     /** Key constant @type Number */
18569     HOME: 36,
18570     /** Key constant @type Number */
18571     LEFT: 37,
18572     /** Key constant @type Number */
18573     UP: 38,
18574     /** Key constant @type Number */
18575     RIGHT: 39,
18576     /** Key constant @type Number */
18577     DOWN: 40,
18578     /** Key constant @type Number */
18579     PRINT_SCREEN: 44,
18580     /** Key constant @type Number */
18581     INSERT: 45,
18582     /** Key constant @type Number */
18583     DELETE: 46,
18584     /** Key constant @type Number */
18585     ZERO: 48,
18586     /** Key constant @type Number */
18587     ONE: 49,
18588     /** Key constant @type Number */
18589     TWO: 50,
18590     /** Key constant @type Number */
18591     THREE: 51,
18592     /** Key constant @type Number */
18593     FOUR: 52,
18594     /** Key constant @type Number */
18595     FIVE: 53,
18596     /** Key constant @type Number */
18597     SIX: 54,
18598     /** Key constant @type Number */
18599     SEVEN: 55,
18600     /** Key constant @type Number */
18601     EIGHT: 56,
18602     /** Key constant @type Number */
18603     NINE: 57,
18604     /** Key constant @type Number */
18605     A: 65,
18606     /** Key constant @type Number */
18607     B: 66,
18608     /** Key constant @type Number */
18609     C: 67,
18610     /** Key constant @type Number */
18611     D: 68,
18612     /** Key constant @type Number */
18613     E: 69,
18614     /** Key constant @type Number */
18615     F: 70,
18616     /** Key constant @type Number */
18617     G: 71,
18618     /** Key constant @type Number */
18619     H: 72,
18620     /** Key constant @type Number */
18621     I: 73,
18622     /** Key constant @type Number */
18623     J: 74,
18624     /** Key constant @type Number */
18625     K: 75,
18626     /** Key constant @type Number */
18627     L: 76,
18628     /** Key constant @type Number */
18629     M: 77,
18630     /** Key constant @type Number */
18631     N: 78,
18632     /** Key constant @type Number */
18633     O: 79,
18634     /** Key constant @type Number */
18635     P: 80,
18636     /** Key constant @type Number */
18637     Q: 81,
18638     /** Key constant @type Number */
18639     R: 82,
18640     /** Key constant @type Number */
18641     S: 83,
18642     /** Key constant @type Number */
18643     T: 84,
18644     /** Key constant @type Number */
18645     U: 85,
18646     /** Key constant @type Number */
18647     V: 86,
18648     /** Key constant @type Number */
18649     W: 87,
18650     /** Key constant @type Number */
18651     X: 88,
18652     /** Key constant @type Number */
18653     Y: 89,
18654     /** Key constant @type Number */
18655     Z: 90,
18656     /** Key constant @type Number */
18657     CONTEXT_MENU: 93,
18658     /** Key constant @type Number */
18659     NUM_ZERO: 96,
18660     /** Key constant @type Number */
18661     NUM_ONE: 97,
18662     /** Key constant @type Number */
18663     NUM_TWO: 98,
18664     /** Key constant @type Number */
18665     NUM_THREE: 99,
18666     /** Key constant @type Number */
18667     NUM_FOUR: 100,
18668     /** Key constant @type Number */
18669     NUM_FIVE: 101,
18670     /** Key constant @type Number */
18671     NUM_SIX: 102,
18672     /** Key constant @type Number */
18673     NUM_SEVEN: 103,
18674     /** Key constant @type Number */
18675     NUM_EIGHT: 104,
18676     /** Key constant @type Number */
18677     NUM_NINE: 105,
18678     /** Key constant @type Number */
18679     NUM_MULTIPLY: 106,
18680     /** Key constant @type Number */
18681     NUM_PLUS: 107,
18682     /** Key constant @type Number */
18683     NUM_MINUS: 109,
18684     /** Key constant @type Number */
18685     NUM_PERIOD: 110,
18686     /** Key constant @type Number */
18687     NUM_DIVISION: 111,
18688     /** Key constant @type Number */
18689     F1: 112,
18690     /** Key constant @type Number */
18691     F2: 113,
18692     /** Key constant @type Number */
18693     F3: 114,
18694     /** Key constant @type Number */
18695     F4: 115,
18696     /** Key constant @type Number */
18697     F5: 116,
18698     /** Key constant @type Number */
18699     F6: 117,
18700     /** Key constant @type Number */
18701     F7: 118,
18702     /** Key constant @type Number */
18703     F8: 119,
18704     /** Key constant @type Number */
18705     F9: 120,
18706     /** Key constant @type Number */
18707     F10: 121,
18708     /** Key constant @type Number */
18709     F11: 122,
18710     /** Key constant @type Number */
18711     F12: 123,
18712     /**
18713      * The mouse wheel delta scaling factor. This value depends on browser version and OS and
18714      * attempts to produce a similar scrolling experience across all platforms and browsers.
18715      *
18716      * To change this value:
18717      *
18718      *      Ext.EventObjectImpl.prototype.WHEEL_SCALE = 72;
18719      *
18720      * @type Number
18721      * @markdown
18722      */
18723     WHEEL_SCALE: (function () {
18724         var scale;
18725
18726         if (Ext.isGecko) {
18727             // Firefox uses 3 on all platforms
18728             scale = 3;
18729         } else if (Ext.isMac) {
18730             // Continuous scrolling devices have momentum and produce much more scroll than
18731             // discrete devices on the same OS and browser. To make things exciting, Safari
18732             // (and not Chrome) changed from small values to 120 (like IE).
18733
18734             if (Ext.isSafari && Ext.webKitVersion >= 532.0) {
18735                 // Safari changed the scrolling factor to match IE (for details see
18736                 // https://bugs.webkit.org/show_bug.cgi?id=24368). The WebKit version where this
18737                 // change was introduced was 532.0
18738                 //      Detailed discussion:
18739                 //      https://bugs.webkit.org/show_bug.cgi?id=29601
18740                 //      http://trac.webkit.org/browser/trunk/WebKit/chromium/src/mac/WebInputEventFactory.mm#L1063
18741                 scale = 120;
18742             } else {
18743                 // MS optical wheel mouse produces multiples of 12 which is close enough
18744                 // to help tame the speed of the continuous mice...
18745                 scale = 12;
18746             }
18747
18748             // Momentum scrolling produces very fast scrolling, so increase the scale factor
18749             // to help produce similar results cross platform. This could be even larger and
18750             // it would help those mice, but other mice would become almost unusable as a
18751             // result (since we cannot tell which device type is in use).
18752             scale *= 3;
18753         } else {
18754             // IE, Opera and other Windows browsers use 120.
18755             scale = 120;
18756         }
18757
18758         return scale;
18759     })(),
18760
18761     /**
18762      * Simple click regex
18763      * @private
18764      */
18765     clickRe: /(dbl)?click/,
18766     // safari keypress events for special keys return bad keycodes
18767     safariKeys: {
18768         3: 13, // enter
18769         63234: 37, // left
18770         63235: 39, // right
18771         63232: 38, // up
18772         63233: 40, // down
18773         63276: 33, // page up
18774         63277: 34, // page down
18775         63272: 46, // delete
18776         63273: 36, // home
18777         63275: 35 // end
18778     },
18779     // normalize button clicks, don't see any way to feature detect this.
18780     btnMap: Ext.isIE ? {
18781         1: 0,
18782         4: 1,
18783         2: 2
18784     } : {
18785         0: 0,
18786         1: 1,
18787         2: 2
18788     },
18789
18790     constructor: function(event, freezeEvent){
18791         if (event) {
18792             this.setEvent(event.browserEvent || event, freezeEvent);
18793         }
18794     },
18795
18796     setEvent: function(event, freezeEvent){
18797         var me = this, button, options;
18798
18799         if (event == me || (event && event.browserEvent)) { // already wrapped
18800             return event;
18801         }
18802         me.browserEvent = event;
18803         if (event) {
18804             // normalize buttons
18805             button = event.button ? me.btnMap[event.button] : (event.which ? event.which - 1 : -1);
18806             if (me.clickRe.test(event.type) && button == -1) {
18807                 button = 0;
18808             }
18809             options = {
18810                 type: event.type,
18811                 button: button,
18812                 shiftKey: event.shiftKey,
18813                 // mac metaKey behaves like ctrlKey
18814                 ctrlKey: event.ctrlKey || event.metaKey || false,
18815                 altKey: event.altKey,
18816                 // in getKey these will be normalized for the mac
18817                 keyCode: event.keyCode,
18818                 charCode: event.charCode,
18819                 // cache the targets for the delayed and or buffered events
18820                 target: Ext.EventManager.getTarget(event),
18821                 relatedTarget: Ext.EventManager.getRelatedTarget(event),
18822                 currentTarget: event.currentTarget,
18823                 xy: (freezeEvent ? me.getXY() : null)
18824             };
18825         } else {
18826             options = {
18827                 button: -1,
18828                 shiftKey: false,
18829                 ctrlKey: false,
18830                 altKey: false,
18831                 keyCode: 0,
18832                 charCode: 0,
18833                 target: null,
18834                 xy: [0, 0]
18835             };
18836         }
18837         Ext.apply(me, options);
18838         return me;
18839     },
18840
18841     /**
18842      * Stop the event (preventDefault and stopPropagation)
18843      */
18844     stopEvent: function(){
18845         this.stopPropagation();
18846         this.preventDefault();
18847     },
18848
18849     /**
18850      * Prevents the browsers default handling of the event.
18851      */
18852     preventDefault: function(){
18853         if (this.browserEvent) {
18854             Ext.EventManager.preventDefault(this.browserEvent);
18855         }
18856     },
18857
18858     /**
18859      * Cancels bubbling of the event.
18860      */
18861     stopPropagation: function(){
18862         var browserEvent = this.browserEvent;
18863
18864         if (browserEvent) {
18865             if (browserEvent.type == 'mousedown') {
18866                 Ext.EventManager.stoppedMouseDownEvent.fire(this);
18867             }
18868             Ext.EventManager.stopPropagation(browserEvent);
18869         }
18870     },
18871
18872     /**
18873      * Gets the character code for the event.
18874      * @return {Number}
18875      */
18876     getCharCode: function(){
18877         return this.charCode || this.keyCode;
18878     },
18879
18880     /**
18881      * Returns a normalized keyCode for the event.
18882      * @return {Number} The key code
18883      */
18884     getKey: function(){
18885         return this.normalizeKey(this.keyCode || this.charCode);
18886     },
18887
18888     /**
18889      * Normalize key codes across browsers
18890      * @private
18891      * @param {Number} key The key code
18892      * @return {Number} The normalized code
18893      */
18894     normalizeKey: function(key){
18895         // can't feature detect this
18896         return Ext.isWebKit ? (this.safariKeys[key] || key) : key;
18897     },
18898
18899     /**
18900      * Gets the x coordinate of the event.
18901      * @return {Number}
18902      * @deprecated 4.0 Replaced by {@link #getX}
18903      */
18904     getPageX: function(){
18905         return this.getX();
18906     },
18907
18908     /**
18909      * Gets the y coordinate of the event.
18910      * @return {Number}
18911      * @deprecated 4.0 Replaced by {@link #getY}
18912      */
18913     getPageY: function(){
18914         return this.getY();
18915     },
18916
18917     /**
18918      * Gets the x coordinate of the event.
18919      * @return {Number}
18920      */
18921     getX: function() {
18922         return this.getXY()[0];
18923     },
18924
18925     /**
18926      * Gets the y coordinate of the event.
18927      * @return {Number}
18928      */
18929     getY: function() {
18930         return this.getXY()[1];
18931     },
18932
18933     /**
18934      * Gets the page coordinates of the event.
18935      * @return {Number[]} The xy values like [x, y]
18936      */
18937     getXY: function() {
18938         if (!this.xy) {
18939             // same for XY
18940             this.xy = Ext.EventManager.getPageXY(this.browserEvent);
18941         }
18942         return this.xy;
18943     },
18944
18945     /**
18946      * Gets the target for the event.
18947      * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
18948      * @param {Number/HTMLElement} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
18949      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
18950      * @return {HTMLElement}
18951      */
18952     getTarget : function(selector, maxDepth, returnEl){
18953         if (selector) {
18954             return Ext.fly(this.target).findParent(selector, maxDepth, returnEl);
18955         }
18956         return returnEl ? Ext.get(this.target) : this.target;
18957     },
18958
18959     /**
18960      * Gets the related target.
18961      * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
18962      * @param {Number/HTMLElement} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
18963      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
18964      * @return {HTMLElement}
18965      */
18966     getRelatedTarget : function(selector, maxDepth, returnEl){
18967         if (selector) {
18968             return Ext.fly(this.relatedTarget).findParent(selector, maxDepth, returnEl);
18969         }
18970         return returnEl ? Ext.get(this.relatedTarget) : this.relatedTarget;
18971     },
18972
18973     /**
18974      * Correctly scales a given wheel delta.
18975      * @param {Number} delta The delta value.
18976      */
18977     correctWheelDelta : function (delta) {
18978         var scale = this.WHEEL_SCALE,
18979             ret = Math.round(delta / scale);
18980
18981         if (!ret && delta) {
18982             ret = (delta < 0) ? -1 : 1; // don't allow non-zero deltas to go to zero!
18983         }
18984
18985         return ret;
18986     },
18987
18988     /**
18989      * Returns the mouse wheel deltas for this event.
18990      * @return {Object} An object with "x" and "y" properties holding the mouse wheel deltas.
18991      */
18992     getWheelDeltas : function () {
18993         var me = this,
18994             event = me.browserEvent,
18995             dx = 0, dy = 0; // the deltas
18996
18997         if (Ext.isDefined(event.wheelDeltaX)) { // WebKit has both dimensions
18998             dx = event.wheelDeltaX;
18999             dy = event.wheelDeltaY;
19000         } else if (event.wheelDelta) { // old WebKit and IE
19001             dy = event.wheelDelta;
19002         } else if (event.detail) { // Gecko
19003             dy = -event.detail; // gecko is backwards
19004
19005             // Gecko sometimes returns really big values if the user changes settings to
19006             // scroll a whole page per scroll
19007             if (dy > 100) {
19008                 dy = 3;
19009             } else if (dy < -100) {
19010                 dy = -3;
19011             }
19012
19013             // Firefox 3.1 adds an axis field to the event to indicate direction of
19014             // scroll.  See https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
19015             if (Ext.isDefined(event.axis) && event.axis === event.HORIZONTAL_AXIS) {
19016                 dx = dy;
19017                 dy = 0;
19018             }
19019         }
19020
19021         return {
19022             x: me.correctWheelDelta(dx),
19023             y: me.correctWheelDelta(dy)
19024         };
19025     },
19026
19027     /**
19028      * Normalizes mouse wheel y-delta across browsers. To get x-delta information, use
19029      * {@link #getWheelDeltas} instead.
19030      * @return {Number} The mouse wheel y-delta
19031      */
19032     getWheelDelta : function(){
19033         var deltas = this.getWheelDeltas();
19034
19035         return deltas.y;
19036     },
19037
19038     /**
19039      * 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.
19040      * Example usage:<pre><code>
19041 // Handle click on any child of an element
19042 Ext.getBody().on('click', function(e){
19043     if(e.within('some-el')){
19044         alert('Clicked on a child of some-el!');
19045     }
19046 });
19047
19048 // Handle click directly on an element, ignoring clicks on child nodes
19049 Ext.getBody().on('click', function(e,t){
19050     if((t.id == 'some-el') && !e.within(t, true)){
19051         alert('Clicked directly on some-el!');
19052     }
19053 });
19054 </code></pre>
19055      * @param {String/HTMLElement/Ext.Element} el The id, DOM element or Ext.Element to check
19056      * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
19057      * @param {Boolean} allowEl (optional) true to also check if the passed element is the target or related target
19058      * @return {Boolean}
19059      */
19060     within : function(el, related, allowEl){
19061         if(el){
19062             var t = related ? this.getRelatedTarget() : this.getTarget(),
19063                 result;
19064
19065             if (t) {
19066                 result = Ext.fly(el).contains(t);
19067                 if (!result && allowEl) {
19068                     result = t == Ext.getDom(el);
19069                 }
19070                 return result;
19071             }
19072         }
19073         return false;
19074     },
19075
19076     /**
19077      * Checks if the key pressed was a "navigation" key
19078      * @return {Boolean} True if the press is a navigation keypress
19079      */
19080     isNavKeyPress : function(){
19081         var me = this,
19082             k = this.normalizeKey(me.keyCode);
19083
19084        return (k >= 33 && k <= 40) ||  // Page Up/Down, End, Home, Left, Up, Right, Down
19085        k == me.RETURN ||
19086        k == me.TAB ||
19087        k == me.ESC;
19088     },
19089
19090     /**
19091      * Checks if the key pressed was a "special" key
19092      * @return {Boolean} True if the press is a special keypress
19093      */
19094     isSpecialKey : function(){
19095         var k = this.normalizeKey(this.keyCode);
19096         return (this.type == 'keypress' && this.ctrlKey) ||
19097         this.isNavKeyPress() ||
19098         (k == this.BACKSPACE) || // Backspace
19099         (k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
19100         (k >= 44 && k <= 46);   // Print Screen, Insert, Delete
19101     },
19102
19103     /**
19104      * Returns a point object that consists of the object coordinates.
19105      * @return {Ext.util.Point} point
19106      */
19107     getPoint : function(){
19108         var xy = this.getXY();
19109         return Ext.create('Ext.util.Point', xy[0], xy[1]);
19110     },
19111
19112    /**
19113     * Returns true if the control, meta, shift or alt key was pressed during this event.
19114     * @return {Boolean}
19115     */
19116     hasModifier : function(){
19117         return this.ctrlKey || this.altKey || this.shiftKey || this.metaKey;
19118     },
19119
19120     /**
19121      * Injects a DOM event using the data in this object and (optionally) a new target.
19122      * This is a low-level technique and not likely to be used by application code. The
19123      * currently supported event types are:
19124      * <p><b>HTMLEvents</b></p>
19125      * <ul>
19126      * <li>load</li>
19127      * <li>unload</li>
19128      * <li>select</li>
19129      * <li>change</li>
19130      * <li>submit</li>
19131      * <li>reset</li>
19132      * <li>resize</li>
19133      * <li>scroll</li>
19134      * </ul>
19135      * <p><b>MouseEvents</b></p>
19136      * <ul>
19137      * <li>click</li>
19138      * <li>dblclick</li>
19139      * <li>mousedown</li>
19140      * <li>mouseup</li>
19141      * <li>mouseover</li>
19142      * <li>mousemove</li>
19143      * <li>mouseout</li>
19144      * </ul>
19145      * <p><b>UIEvents</b></p>
19146      * <ul>
19147      * <li>focusin</li>
19148      * <li>focusout</li>
19149      * <li>activate</li>
19150      * <li>focus</li>
19151      * <li>blur</li>
19152      * </ul>
19153      * @param {Ext.Element/HTMLElement} target (optional) If specified, the target for the event. This
19154      * is likely to be used when relaying a DOM event. If not specified, {@link #getTarget}
19155      * is used to determine the target.
19156      */
19157     injectEvent: function () {
19158         var API,
19159             dispatchers = {}; // keyed by event type (e.g., 'mousedown')
19160
19161         // Good reference: http://developer.yahoo.com/yui/docs/UserAction.js.html
19162
19163         // IE9 has createEvent, but this code causes major problems with htmleditor (it
19164         // blocks all mouse events and maybe more). TODO
19165
19166         if (!Ext.isIE && document.createEvent) { // if (DOM compliant)
19167             API = {
19168                 createHtmlEvent: function (doc, type, bubbles, cancelable) {
19169                     var event = doc.createEvent('HTMLEvents');
19170
19171                     event.initEvent(type, bubbles, cancelable);
19172                     return event;
19173                 },
19174
19175                 createMouseEvent: function (doc, type, bubbles, cancelable, detail,
19176                                             clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
19177                                             button, relatedTarget) {
19178                     var event = doc.createEvent('MouseEvents'),
19179                         view = doc.defaultView || window;
19180
19181                     if (event.initMouseEvent) {
19182                         event.initMouseEvent(type, bubbles, cancelable, view, detail,
19183                                     clientX, clientY, clientX, clientY, ctrlKey, altKey,
19184                                     shiftKey, metaKey, button, relatedTarget);
19185                     } else { // old Safari
19186                         event = doc.createEvent('UIEvents');
19187                         event.initEvent(type, bubbles, cancelable);
19188                         event.view = view;
19189                         event.detail = detail;
19190                         event.screenX = clientX;
19191                         event.screenY = clientY;
19192                         event.clientX = clientX;
19193                         event.clientY = clientY;
19194                         event.ctrlKey = ctrlKey;
19195                         event.altKey = altKey;
19196                         event.metaKey = metaKey;
19197                         event.shiftKey = shiftKey;
19198                         event.button = button;
19199                         event.relatedTarget = relatedTarget;
19200                     }
19201
19202                     return event;
19203                 },
19204
19205                 createUIEvent: function (doc, type, bubbles, cancelable, detail) {
19206                     var event = doc.createEvent('UIEvents'),
19207                         view = doc.defaultView || window;
19208
19209                     event.initUIEvent(type, bubbles, cancelable, view, detail);
19210                     return event;
19211                 },
19212
19213                 fireEvent: function (target, type, event) {
19214                     target.dispatchEvent(event);
19215                 },
19216
19217                 fixTarget: function (target) {
19218                     // Safari3 doesn't have window.dispatchEvent()
19219                     if (target == window && !target.dispatchEvent) {
19220                         return document;
19221                     }
19222
19223                     return target;
19224                 }
19225             };
19226         } else if (document.createEventObject) { // else if (IE)
19227             var crazyIEButtons = { 0: 1, 1: 4, 2: 2 };
19228
19229             API = {
19230                 createHtmlEvent: function (doc, type, bubbles, cancelable) {
19231                     var event = doc.createEventObject();
19232                     event.bubbles = bubbles;
19233                     event.cancelable = cancelable;
19234                     return event;
19235                 },
19236
19237                 createMouseEvent: function (doc, type, bubbles, cancelable, detail,
19238                                             clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
19239                                             button, relatedTarget) {
19240                     var event = doc.createEventObject();
19241                     event.bubbles = bubbles;
19242                     event.cancelable = cancelable;
19243                     event.detail = detail;
19244                     event.screenX = clientX;
19245                     event.screenY = clientY;
19246                     event.clientX = clientX;
19247                     event.clientY = clientY;
19248                     event.ctrlKey = ctrlKey;
19249                     event.altKey = altKey;
19250                     event.shiftKey = shiftKey;
19251                     event.metaKey = metaKey;
19252                     event.button = crazyIEButtons[button] || button;
19253                     event.relatedTarget = relatedTarget; // cannot assign to/fromElement
19254                     return event;
19255                 },
19256
19257                 createUIEvent: function (doc, type, bubbles, cancelable, detail) {
19258                     var event = doc.createEventObject();
19259                     event.bubbles = bubbles;
19260                     event.cancelable = cancelable;
19261                     return event;
19262                 },
19263
19264                 fireEvent: function (target, type, event) {
19265                     target.fireEvent('on' + type, event);
19266                 },
19267
19268                 fixTarget: function (target) {
19269                     if (target == document) {
19270                         // IE6,IE7 thinks window==document and doesn't have window.fireEvent()
19271                         // IE6,IE7 cannot properly call document.fireEvent()
19272                         return document.documentElement;
19273                     }
19274
19275                     return target;
19276                 }
19277             };
19278         }
19279
19280         //----------------
19281         // HTMLEvents
19282
19283         Ext.Object.each({
19284                 load:   [false, false],
19285                 unload: [false, false],
19286                 select: [true, false],
19287                 change: [true, false],
19288                 submit: [true, true],
19289                 reset:  [true, false],
19290                 resize: [true, false],
19291                 scroll: [true, false]
19292             },
19293             function (name, value) {
19294                 var bubbles = value[0], cancelable = value[1];
19295                 dispatchers[name] = function (targetEl, srcEvent) {
19296                     var e = API.createHtmlEvent(name, bubbles, cancelable);
19297                     API.fireEvent(targetEl, name, e);
19298                 };
19299             });
19300
19301         //----------------
19302         // MouseEvents
19303
19304         function createMouseEventDispatcher (type, detail) {
19305             var cancelable = (type != 'mousemove');
19306             return function (targetEl, srcEvent) {
19307                 var xy = srcEvent.getXY(),
19308                     e = API.createMouseEvent(targetEl.ownerDocument, type, true, cancelable,
19309                                 detail, xy[0], xy[1], srcEvent.ctrlKey, srcEvent.altKey,
19310                                 srcEvent.shiftKey, srcEvent.metaKey, srcEvent.button,
19311                                 srcEvent.relatedTarget);
19312                 API.fireEvent(targetEl, type, e);
19313             };
19314         }
19315
19316         Ext.each(['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout'],
19317             function (eventName) {
19318                 dispatchers[eventName] = createMouseEventDispatcher(eventName, 1);
19319             });
19320
19321         //----------------
19322         // UIEvents
19323
19324         Ext.Object.each({
19325                 focusin:  [true, false],
19326                 focusout: [true, false],
19327                 activate: [true, true],
19328                 focus:    [false, false],
19329                 blur:     [false, false]
19330             },
19331             function (name, value) {
19332                 var bubbles = value[0], cancelable = value[1];
19333                 dispatchers[name] = function (targetEl, srcEvent) {
19334                     var e = API.createUIEvent(targetEl.ownerDocument, name, bubbles, cancelable, 1);
19335                     API.fireEvent(targetEl, name, e);
19336                 };
19337             });
19338
19339         //---------
19340         if (!API) {
19341             // not even sure what ancient browsers fall into this category...
19342
19343             dispatchers = {}; // never mind all those we just built :P
19344
19345             API = {
19346                 fixTarget: function (t) {
19347                     return t;
19348                 }
19349             };
19350         }
19351
19352         function cannotInject (target, srcEvent) {
19353             // TODO log something
19354         }
19355
19356         return function (target) {
19357             var me = this,
19358                 dispatcher = dispatchers[me.type] || cannotInject,
19359                 t = target ? (target.dom || target) : me.getTarget();
19360
19361             t = API.fixTarget(t);
19362             dispatcher(t, me);
19363         };
19364     }() // call to produce method
19365
19366 }, function() {
19367
19368 Ext.EventObject = new Ext.EventObjectImpl();
19369
19370 });
19371
19372
19373 /**
19374  * @class Ext.Element
19375  */
19376 (function(){
19377     var doc = document,
19378         activeElement = null,
19379         isCSS1 = doc.compatMode == "CSS1Compat",
19380         ELEMENT = Ext.Element,
19381         fly = function(el){
19382             if (!_fly) {
19383                 _fly = new Ext.Element.Flyweight();
19384             }
19385             _fly.dom = el;
19386             return _fly;
19387         }, _fly;
19388
19389     // If the browser does not support document.activeElement we need some assistance.
19390     // This covers old Safari 3.2 (4.0 added activeElement along with just about all
19391     // other browsers). We need this support to handle issues with old Safari.
19392     if (!('activeElement' in doc) && doc.addEventListener) {
19393         doc.addEventListener('focus',
19394             function (ev) {
19395                 if (ev && ev.target) {
19396                     activeElement = (ev.target == doc) ? null : ev.target;
19397                 }
19398             }, true);
19399     }
19400
19401     /*
19402      * Helper function to create the function that will restore the selection.
19403      */
19404     function makeSelectionRestoreFn (activeEl, start, end) {
19405         return function () {
19406             activeEl.selectionStart = start;
19407             activeEl.selectionEnd = end;
19408         };
19409     }
19410
19411     Ext.apply(ELEMENT, {
19412         isAncestor : function(p, c) {
19413             var ret = false;
19414
19415             p = Ext.getDom(p);
19416             c = Ext.getDom(c);
19417             if (p && c) {
19418                 if (p.contains) {
19419                     return p.contains(c);
19420                 } else if (p.compareDocumentPosition) {
19421                     return !!(p.compareDocumentPosition(c) & 16);
19422                 } else {
19423                     while ((c = c.parentNode)) {
19424                         ret = c == p || ret;
19425                     }
19426                 }
19427             }
19428             return ret;
19429         },
19430
19431         /**
19432          * Returns the active element in the DOM. If the browser supports activeElement
19433          * on the document, this is returned. If not, the focus is tracked and the active
19434          * element is maintained internally.
19435          * @return {HTMLElement} The active (focused) element in the document.
19436          */
19437         getActiveElement: function () {
19438             return doc.activeElement || activeElement;
19439         },
19440
19441         /**
19442          * Creates a function to call to clean up problems with the work-around for the
19443          * WebKit RightMargin bug. The work-around is to add "display: 'inline-block'" to
19444          * the element before calling getComputedStyle and then to restore its original
19445          * display value. The problem with this is that it corrupts the selection of an
19446          * INPUT or TEXTAREA element (as in the "I-beam" goes away but ths focus remains).
19447          * To cleanup after this, we need to capture the selection of any such element and
19448          * then restore it after we have restored the display style.
19449          *
19450          * @param target {Element} The top-most element being adjusted.
19451          * @private
19452          */
19453         getRightMarginFixCleaner: function (target) {
19454             var supports = Ext.supports,
19455                 hasInputBug = supports.DisplayChangeInputSelectionBug,
19456                 hasTextAreaBug = supports.DisplayChangeTextAreaSelectionBug;
19457
19458             if (hasInputBug || hasTextAreaBug) {
19459                 var activeEl = doc.activeElement || activeElement, // save a call
19460                     tag = activeEl && activeEl.tagName,
19461                     start,
19462                     end;
19463
19464                 if ((hasTextAreaBug && tag == 'TEXTAREA') ||
19465                     (hasInputBug && tag == 'INPUT' && activeEl.type == 'text')) {
19466                     if (ELEMENT.isAncestor(target, activeEl)) {
19467                         start = activeEl.selectionStart;
19468                         end = activeEl.selectionEnd;
19469
19470                         if (Ext.isNumber(start) && Ext.isNumber(end)) { // to be safe...
19471                             // We don't create the raw closure here inline because that
19472                             // will be costly even if we don't want to return it (nested
19473                             // function decls and exprs are often instantiated on entry
19474                             // regardless of whether execution ever reaches them):
19475                             return makeSelectionRestoreFn(activeEl, start, end);
19476                         }
19477                     }
19478                 }
19479             }
19480
19481             return Ext.emptyFn; // avoid special cases, just return a nop
19482         },
19483
19484         getViewWidth : function(full) {
19485             return full ? ELEMENT.getDocumentWidth() : ELEMENT.getViewportWidth();
19486         },
19487
19488         getViewHeight : function(full) {
19489             return full ? ELEMENT.getDocumentHeight() : ELEMENT.getViewportHeight();
19490         },
19491
19492         getDocumentHeight: function() {
19493             return Math.max(!isCSS1 ? doc.body.scrollHeight : doc.documentElement.scrollHeight, ELEMENT.getViewportHeight());
19494         },
19495
19496         getDocumentWidth: function() {
19497             return Math.max(!isCSS1 ? doc.body.scrollWidth : doc.documentElement.scrollWidth, ELEMENT.getViewportWidth());
19498         },
19499
19500         getViewportHeight: function(){
19501             return Ext.isIE ?
19502                    (Ext.isStrict ? doc.documentElement.clientHeight : doc.body.clientHeight) :
19503                    self.innerHeight;
19504         },
19505
19506         getViewportWidth : function() {
19507             return (!Ext.isStrict && !Ext.isOpera) ? doc.body.clientWidth :
19508                    Ext.isIE ? doc.documentElement.clientWidth : self.innerWidth;
19509         },
19510
19511         getY : function(el) {
19512             return ELEMENT.getXY(el)[1];
19513         },
19514
19515         getX : function(el) {
19516             return ELEMENT.getXY(el)[0];
19517         },
19518
19519         getOffsetParent: function (el) {
19520             el = Ext.getDom(el);
19521             try {
19522                 // accessing offsetParent can throw "Unspecified Error" in IE6-8 (not 9)
19523                 return el.offsetParent;
19524             } catch (e) {
19525                 var body = document.body; // safe bet, unless...
19526                 return (el == body) ? null : body;
19527             }
19528         },
19529
19530         getXY : function(el) {
19531             var p,
19532                 pe,
19533                 b,
19534                 bt,
19535                 bl,
19536                 dbd,
19537                 x = 0,
19538                 y = 0,
19539                 scroll,
19540                 hasAbsolute,
19541                 bd = (doc.body || doc.documentElement),
19542                 ret;
19543
19544             el = Ext.getDom(el);
19545
19546             if(el != bd){
19547                 hasAbsolute = fly(el).isStyle("position", "absolute");
19548
19549                 if (el.getBoundingClientRect) {
19550                     try {
19551                         b = el.getBoundingClientRect();
19552                         scroll = fly(document).getScroll();
19553                         ret = [ Math.round(b.left + scroll.left), Math.round(b.top + scroll.top) ];
19554                     } catch (e) {
19555                         // IE6-8 can also throw from getBoundingClientRect...
19556                     }
19557                 }
19558
19559                 if (!ret) {
19560                     for (p = el; p; p = ELEMENT.getOffsetParent(p)) {
19561                         pe = fly(p);
19562                         x += p.offsetLeft;
19563                         y += p.offsetTop;
19564
19565                         hasAbsolute = hasAbsolute || pe.isStyle("position", "absolute");
19566
19567                         if (Ext.isGecko) {
19568                             y += bt = parseInt(pe.getStyle("borderTopWidth"), 10) || 0;
19569                             x += bl = parseInt(pe.getStyle("borderLeftWidth"), 10) || 0;
19570
19571                             if (p != el && !pe.isStyle('overflow','visible')) {
19572                                 x += bl;
19573                                 y += bt;
19574                             }
19575                         }
19576                     }
19577
19578                     if (Ext.isSafari && hasAbsolute) {
19579                         x -= bd.offsetLeft;
19580                         y -= bd.offsetTop;
19581                     }
19582
19583                     if (Ext.isGecko && !hasAbsolute) {
19584                         dbd = fly(bd);
19585                         x += parseInt(dbd.getStyle("borderLeftWidth"), 10) || 0;
19586                         y += parseInt(dbd.getStyle("borderTopWidth"), 10) || 0;
19587                     }
19588
19589                     p = el.parentNode;
19590                     while (p && p != bd) {
19591                         if (!Ext.isOpera || (p.tagName != 'TR' && !fly(p).isStyle("display", "inline"))) {
19592                             x -= p.scrollLeft;
19593                             y -= p.scrollTop;
19594                         }
19595                         p = p.parentNode;
19596                     }
19597                     ret = [x,y];
19598                 }
19599             }
19600             return ret || [0,0];
19601         },
19602
19603         setXY : function(el, xy) {
19604             (el = Ext.fly(el, '_setXY')).position();
19605
19606             var pts = el.translatePoints(xy),
19607                 style = el.dom.style,
19608                 pos;
19609
19610             for (pos in pts) {
19611                 if (!isNaN(pts[pos])) {
19612                     style[pos] = pts[pos] + "px";
19613                 }
19614             }
19615         },
19616
19617         setX : function(el, x) {
19618             ELEMENT.setXY(el, [x, false]);
19619         },
19620
19621         setY : function(el, y) {
19622             ELEMENT.setXY(el, [false, y]);
19623         },
19624
19625         /**
19626          * Serializes a DOM form into a url encoded string
19627          * @param {Object} form The form
19628          * @return {String} The url encoded form
19629          */
19630         serializeForm: function(form) {
19631             var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements,
19632                 hasSubmit = false,
19633                 encoder = encodeURIComponent,
19634                 name,
19635                 data = '',
19636                 type,
19637                 hasValue;
19638
19639             Ext.each(fElements, function(element){
19640                 name = element.name;
19641                 type = element.type;
19642
19643                 if (!element.disabled && name) {
19644                     if (/select-(one|multiple)/i.test(type)) {
19645                         Ext.each(element.options, function(opt){
19646                             if (opt.selected) {
19647                                 hasValue = opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttributeNode('value').specified;
19648                                 data += Ext.String.format("{0}={1}&", encoder(name), encoder(hasValue ? opt.value : opt.text));
19649                             }
19650                         });
19651                     } else if (!(/file|undefined|reset|button/i.test(type))) {
19652                         if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)) {
19653                             data += encoder(name) + '=' + encoder(element.value) + '&';
19654                             hasSubmit = /submit/i.test(type);
19655                         }
19656                     }
19657                 }
19658             });
19659             return data.substr(0, data.length - 1);
19660         }
19661     });
19662 })();
19663
19664 /**
19665  * @class Ext.Element
19666  */
19667
19668 Ext.Element.addMethods((function(){
19669     var focusRe = /button|input|textarea|select|object/;
19670     return {
19671         /**
19672          * Monitors this Element for the mouse leaving. Calls the function after the specified delay only if
19673          * the mouse was not moved back into the Element within the delay. If the mouse <i>was</i> moved
19674          * back in, the function is not called.
19675          * @param {Number} delay The delay <b>in milliseconds</b> to wait for possible mouse re-entry before calling the handler function.
19676          * @param {Function} handler The function to call if the mouse remains outside of this Element for the specified time.
19677          * @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to this Element.
19678          * @return {Object} The listeners object which was added to this element so that monitoring can be stopped. Example usage:<pre><code>
19679 // Hide the menu if the mouse moves out for 250ms or more
19680 this.mouseLeaveMonitor = this.menuEl.monitorMouseLeave(250, this.hideMenu, this);
19681
19682 ...
19683 // Remove mouseleave monitor on menu destroy
19684 this.menuEl.un(this.mouseLeaveMonitor);
19685     </code></pre>
19686          */
19687         monitorMouseLeave: function(delay, handler, scope) {
19688             var me = this,
19689                 timer,
19690                 listeners = {
19691                     mouseleave: function(e) {
19692                         timer = setTimeout(Ext.Function.bind(handler, scope||me, [e]), delay);
19693                     },
19694                     mouseenter: function() {
19695                         clearTimeout(timer);
19696                     },
19697                     freezeEvent: true
19698                 };
19699
19700             me.on(listeners);
19701             return listeners;
19702         },
19703
19704         /**
19705          * Stops the specified event(s) from bubbling and optionally prevents the default action
19706          * @param {String/String[]} eventName an event / array of events to stop from bubbling
19707          * @param {Boolean} preventDefault (optional) true to prevent the default action too
19708          * @return {Ext.Element} this
19709          */
19710         swallowEvent : function(eventName, preventDefault) {
19711             var me = this;
19712             function fn(e) {
19713                 e.stopPropagation();
19714                 if (preventDefault) {
19715                     e.preventDefault();
19716                 }
19717             }
19718
19719             if (Ext.isArray(eventName)) {
19720                 Ext.each(eventName, function(e) {
19721                      me.on(e, fn);
19722                 });
19723                 return me;
19724             }
19725             me.on(eventName, fn);
19726             return me;
19727         },
19728
19729         /**
19730          * Create an event handler on this element such that when the event fires and is handled by this element,
19731          * it will be relayed to another object (i.e., fired again as if it originated from that object instead).
19732          * @param {String} eventName The type of event to relay
19733          * @param {Object} object Any object that extends {@link Ext.util.Observable} that will provide the context
19734          * for firing the relayed event
19735          */
19736         relayEvent : function(eventName, observable) {
19737             this.on(eventName, function(e) {
19738                 observable.fireEvent(eventName, e);
19739             });
19740         },
19741
19742         /**
19743          * Removes Empty, or whitespace filled text nodes. Combines adjacent text nodes.
19744          * @param {Boolean} forceReclean (optional) By default the element
19745          * keeps track if it has been cleaned already so
19746          * you can call this over and over. However, if you update the element and
19747          * need to force a reclean, you can pass true.
19748          */
19749         clean : function(forceReclean) {
19750             var me  = this,
19751                 dom = me.dom,
19752                 n   = dom.firstChild,
19753                 nx,
19754                 ni  = -1;
19755     
19756             if (Ext.Element.data(dom, 'isCleaned') && forceReclean !== true) {
19757                 return me;
19758             }
19759
19760             while (n) {
19761                 nx = n.nextSibling;
19762                 if (n.nodeType == 3) {
19763                     // Remove empty/whitespace text nodes
19764                     if (!(/\S/.test(n.nodeValue))) {
19765                         dom.removeChild(n);
19766                     // Combine adjacent text nodes
19767                     } else if (nx && nx.nodeType == 3) {
19768                         n.appendData(Ext.String.trim(nx.data));
19769                         dom.removeChild(nx);
19770                         nx = n.nextSibling;
19771                         n.nodeIndex = ++ni;
19772                     }
19773                 } else {
19774                     // Recursively clean
19775                     Ext.fly(n).clean();
19776                     n.nodeIndex = ++ni;
19777                 }
19778                 n = nx;
19779             }
19780
19781             Ext.Element.data(dom, 'isCleaned', true);
19782             return me;
19783         },
19784
19785         /**
19786          * Direct access to the Ext.ElementLoader {@link Ext.ElementLoader#load} method. The method takes the same object
19787          * parameter as {@link Ext.ElementLoader#load}
19788          * @return {Ext.Element} this
19789          */
19790         load : function(options) {
19791             this.getLoader().load(options);
19792             return this;
19793         },
19794
19795         /**
19796         * Gets this element's {@link Ext.ElementLoader ElementLoader}
19797         * @return {Ext.ElementLoader} The loader
19798         */
19799         getLoader : function() {
19800             var dom = this.dom,
19801                 data = Ext.Element.data,
19802                 loader = data(dom, 'loader');
19803     
19804             if (!loader) {
19805                 loader = Ext.create('Ext.ElementLoader', {
19806                     target: this
19807                 });
19808                 data(dom, 'loader', loader);
19809             }
19810             return loader;
19811         },
19812
19813         /**
19814         * Update the innerHTML of this element, optionally searching for and processing scripts
19815         * @param {String} html The new HTML
19816         * @param {Boolean} [loadScripts=false] True to look for and process scripts
19817         * @param {Function} [callback] For async script loading you can be notified when the update completes
19818         * @return {Ext.Element} this
19819          */
19820         update : function(html, loadScripts, callback) {
19821             var me = this,
19822                 id,
19823                 dom,
19824                 interval;
19825
19826             if (!me.dom) {
19827                 return me;
19828             }
19829             html = html || '';
19830             dom = me.dom;
19831
19832             if (loadScripts !== true) {
19833                 dom.innerHTML = html;
19834                 Ext.callback(callback, me);
19835                 return me;
19836             }
19837
19838             id  = Ext.id();
19839             html += '<span id="' + id + '"></span>';
19840
19841             interval = setInterval(function(){
19842                 if (!document.getElementById(id)) {
19843                     return false;
19844                 }
19845                 clearInterval(interval);
19846                 var DOC    = document,
19847                     hd     = DOC.getElementsByTagName("head")[0],
19848                     re     = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
19849                     srcRe  = /\ssrc=([\'\"])(.*?)\1/i,
19850                     typeRe = /\stype=([\'\"])(.*?)\1/i,
19851                     match,
19852                     attrs,
19853                     srcMatch,
19854                     typeMatch,
19855                     el,
19856                     s;
19857
19858                 while ((match = re.exec(html))) {
19859                     attrs = match[1];
19860                     srcMatch = attrs ? attrs.match(srcRe) : false;
19861                     if (srcMatch && srcMatch[2]) {
19862                        s = DOC.createElement("script");
19863                        s.src = srcMatch[2];
19864                        typeMatch = attrs.match(typeRe);
19865                        if (typeMatch && typeMatch[2]) {
19866                            s.type = typeMatch[2];
19867                        }
19868                        hd.appendChild(s);
19869                     } else if (match[2] && match[2].length > 0) {
19870                         if (window.execScript) {
19871                            window.execScript(match[2]);
19872                         } else {
19873                            window.eval(match[2]);
19874                         }
19875                     }
19876                 }
19877
19878                 el = DOC.getElementById(id);
19879                 if (el) {
19880                     Ext.removeNode(el);
19881                 }
19882                 Ext.callback(callback, me);
19883             }, 20);
19884             dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, '');
19885             return me;
19886         },
19887
19888         // inherit docs, overridden so we can add removeAnchor
19889         removeAllListeners : function() {
19890             this.removeAnchor();
19891             Ext.EventManager.removeAll(this.dom);
19892             return this;
19893         },
19894     
19895         /**
19896          * Gets the parent node of the current element taking into account Ext.scopeResetCSS
19897          * @protected
19898          * @return {HTMLElement} The parent element
19899          */
19900         getScopeParent: function(){
19901             var parent = this.dom.parentNode;
19902             return Ext.scopeResetCSS ? parent.parentNode : parent;
19903         },
19904
19905         /**
19906          * Creates a proxy element of this element
19907          * @param {String/Object} config The class name of the proxy element or a DomHelper config object
19908          * @param {String/HTMLElement} [renderTo] The element or element id to render the proxy to (defaults to document.body)
19909          * @param {Boolean} [matchBox=false] True to align and size the proxy to this element now.
19910          * @return {Ext.Element} The new proxy element
19911          */
19912         createProxy : function(config, renderTo, matchBox) {
19913             config = (typeof config == 'object') ? config : {tag : "div", cls: config};
19914
19915             var me = this,
19916                 proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
19917                                    Ext.DomHelper.insertBefore(me.dom, config, true);
19918
19919             proxy.setVisibilityMode(Ext.Element.DISPLAY);
19920             proxy.hide();
19921             if (matchBox && me.setBox && me.getBox) { // check to make sure Element.position.js is loaded
19922                proxy.setBox(me.getBox());
19923             }
19924             return proxy;
19925         },
19926     
19927         /**
19928          * Checks whether this element can be focused.
19929          * @return {Boolean} True if the element is focusable
19930          */
19931         focusable: function(){
19932             var dom = this.dom,
19933                 nodeName = dom.nodeName.toLowerCase(),
19934                 canFocus = false,
19935                 hasTabIndex = !isNaN(dom.tabIndex);
19936             
19937             if (!dom.disabled) {
19938                 if (focusRe.test(nodeName)) {
19939                     canFocus = true;
19940                 } else {
19941                     canFocus = nodeName == 'a' ? dom.href || hasTabIndex : hasTabIndex;
19942                 }
19943             }
19944             return canFocus && this.isVisible(true);
19945         }    
19946     };
19947 })());
19948 Ext.Element.prototype.clearListeners = Ext.Element.prototype.removeAllListeners;
19949
19950 /**
19951  * @class Ext.Element
19952  */
19953 Ext.Element.addMethods({
19954     /**
19955      * Gets the x,y coordinates specified by the anchor position on the element.
19956      * @param {String} [anchor='c'] The specified anchor position.  See {@link #alignTo}
19957      * for details on supported anchor positions.
19958      * @param {Boolean} [local] True to get the local (element top/left-relative) anchor position instead
19959      * of page coordinates
19960      * @param {Object} [size] An object containing the size to use for calculating anchor position
19961      * {width: (target width), height: (target height)} (defaults to the element's current size)
19962      * @return {Number[]} [x, y] An array containing the element's x and y coordinates
19963      */
19964     getAnchorXY : function(anchor, local, s){
19965         //Passing a different size is useful for pre-calculating anchors,
19966         //especially for anchored animations that change the el size.
19967         anchor = (anchor || "tl").toLowerCase();
19968         s = s || {};
19969
19970         var me = this,
19971             vp = me.dom == document.body || me.dom == document,
19972             w = s.width || vp ? Ext.Element.getViewWidth() : me.getWidth(),
19973             h = s.height || vp ? Ext.Element.getViewHeight() : me.getHeight(),
19974             xy,
19975             r = Math.round,
19976             o = me.getXY(),
19977             scroll = me.getScroll(),
19978             extraX = vp ? scroll.left : !local ? o[0] : 0,
19979             extraY = vp ? scroll.top : !local ? o[1] : 0,
19980             hash = {
19981                 c  : [r(w * 0.5), r(h * 0.5)],
19982                 t  : [r(w * 0.5), 0],
19983                 l  : [0, r(h * 0.5)],
19984                 r  : [w, r(h * 0.5)],
19985                 b  : [r(w * 0.5), h],
19986                 tl : [0, 0],
19987                 bl : [0, h],
19988                 br : [w, h],
19989                 tr : [w, 0]
19990             };
19991
19992         xy = hash[anchor];
19993         return [xy[0] + extraX, xy[1] + extraY];
19994     },
19995
19996     /**
19997      * Anchors an element to another element and realigns it when the window is resized.
19998      * @param {String/HTMLElement/Ext.Element} element The element to align to.
19999      * @param {String} position The position to align to.
20000      * @param {Number[]} [offsets] Offset the positioning by [x, y]
20001      * @param {Boolean/Object} [animate] True for the default animation or a standard Element animation config object
20002      * @param {Boolean/Number} [monitorScroll] True to monitor body scroll and reposition. If this parameter
20003      * is a number, it is used as the buffer delay (defaults to 50ms).
20004      * @param {Function} [callback] The function to call after the animation finishes
20005      * @return {Ext.Element} this
20006      */
20007     anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
20008         var me = this,
20009             dom = me.dom,
20010             scroll = !Ext.isEmpty(monitorScroll),
20011             action = function(){
20012                 Ext.fly(dom).alignTo(el, alignment, offsets, animate);
20013                 Ext.callback(callback, Ext.fly(dom));
20014             },
20015             anchor = this.getAnchor();
20016
20017         // previous listener anchor, remove it
20018         this.removeAnchor();
20019         Ext.apply(anchor, {
20020             fn: action,
20021             scroll: scroll
20022         });
20023
20024         Ext.EventManager.onWindowResize(action, null);
20025
20026         if(scroll){
20027             Ext.EventManager.on(window, 'scroll', action, null,
20028                 {buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
20029         }
20030         action.call(me); // align immediately
20031         return me;
20032     },
20033
20034     /**
20035      * Remove any anchor to this element. See {@link #anchorTo}.
20036      * @return {Ext.Element} this
20037      */
20038     removeAnchor : function(){
20039         var me = this,
20040             anchor = this.getAnchor();
20041
20042         if(anchor && anchor.fn){
20043             Ext.EventManager.removeResizeListener(anchor.fn);
20044             if(anchor.scroll){
20045                 Ext.EventManager.un(window, 'scroll', anchor.fn);
20046             }
20047             delete anchor.fn;
20048         }
20049         return me;
20050     },
20051
20052     // private
20053     getAnchor : function(){
20054         var data = Ext.Element.data,
20055             dom = this.dom;
20056             if (!dom) {
20057                 return;
20058             }
20059             var anchor = data(dom, '_anchor');
20060
20061         if(!anchor){
20062             anchor = data(dom, '_anchor', {});
20063         }
20064         return anchor;
20065     },
20066
20067     getAlignVector: function(el, spec, offset) {
20068         var me = this,
20069             side = {t:"top", l:"left", r:"right", b: "bottom"},
20070             thisRegion = me.getRegion(),
20071             elRegion;
20072
20073         el = Ext.get(el);
20074         if(!el || !el.dom){
20075             Ext.Error.raise({
20076                 sourceClass: 'Ext.Element',
20077                 sourceMethod: 'getAlignVector',
20078                 msg: 'Attempted to align an element that doesn\'t exist'
20079             });
20080         }
20081
20082         elRegion = el.getRegion();
20083     },
20084
20085     /**
20086      * Gets the x,y coordinates to align this element with another element. See {@link #alignTo} for more info on the
20087      * supported position values.
20088      * @param {String/HTMLElement/Ext.Element} element The element to align to.
20089      * @param {String} [position="tl-bl?"] The position to align to (defaults to )
20090      * @param {Number[]} [offsets] Offset the positioning by [x, y]
20091      * @return {Number[]} [x, y]
20092      */
20093     getAlignToXY : function(el, p, o){
20094         el = Ext.get(el);
20095
20096         if(!el || !el.dom){
20097             Ext.Error.raise({
20098                 sourceClass: 'Ext.Element',
20099                 sourceMethod: 'getAlignToXY',
20100                 msg: 'Attempted to align an element that doesn\'t exist'
20101             });
20102         }
20103
20104         o = o || [0,0];
20105         p = (!p || p == "?" ? "tl-bl?" : (!(/-/).test(p) && p !== "" ? "tl-" + p : p || "tl-bl")).toLowerCase();
20106
20107         var me = this,
20108             d = me.dom,
20109             a1,
20110             a2,
20111             x,
20112             y,
20113             //constrain the aligned el to viewport if necessary
20114             w,
20115             h,
20116             r,
20117             dw = Ext.Element.getViewWidth() -10, // 10px of margin for ie
20118             dh = Ext.Element.getViewHeight()-10, // 10px of margin for ie
20119             p1y,
20120             p1x,
20121             p2y,
20122             p2x,
20123             swapY,
20124             swapX,
20125             doc = document,
20126             docElement = doc.documentElement,
20127             docBody = doc.body,
20128             scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0)+5,
20129             scrollY = (docElement.scrollTop || docBody.scrollTop || 0)+5,
20130             c = false, //constrain to viewport
20131             p1 = "",
20132             p2 = "",
20133             m = p.match(/^([a-z]+)-([a-z]+)(\?)?$/);
20134
20135         if(!m){
20136             Ext.Error.raise({
20137                 sourceClass: 'Ext.Element',
20138                 sourceMethod: 'getAlignToXY',
20139                 el: el,
20140                 position: p,
20141                 offset: o,
20142                 msg: 'Attemmpted to align an element with an invalid position: "' + p + '"'
20143             });
20144         }
20145
20146         p1 = m[1];
20147         p2 = m[2];
20148         c = !!m[3];
20149
20150         //Subtract the aligned el's internal xy from the target's offset xy
20151         //plus custom offset to get the aligned el's new offset xy
20152         a1 = me.getAnchorXY(p1, true);
20153         a2 = el.getAnchorXY(p2, false);
20154
20155         x = a2[0] - a1[0] + o[0];
20156         y = a2[1] - a1[1] + o[1];
20157
20158         if(c){
20159            w = me.getWidth();
20160            h = me.getHeight();
20161            r = el.getRegion();
20162            //If we are at a viewport boundary and the aligned el is anchored on a target border that is
20163            //perpendicular to the vp border, allow the aligned el to slide on that border,
20164            //otherwise swap the aligned el to the opposite border of the target.
20165            p1y = p1.charAt(0);
20166            p1x = p1.charAt(p1.length-1);
20167            p2y = p2.charAt(0);
20168            p2x = p2.charAt(p2.length-1);
20169            swapY = ((p1y=="t" && p2y=="b") || (p1y=="b" && p2y=="t"));
20170            swapX = ((p1x=="r" && p2x=="l") || (p1x=="l" && p2x=="r"));
20171
20172
20173            if (x + w > dw + scrollX) {
20174                 x = swapX ? r.left-w : dw+scrollX-w;
20175            }
20176            if (x < scrollX) {
20177                x = swapX ? r.right : scrollX;
20178            }
20179            if (y + h > dh + scrollY) {
20180                 y = swapY ? r.top-h : dh+scrollY-h;
20181             }
20182            if (y < scrollY){
20183                y = swapY ? r.bottom : scrollY;
20184            }
20185         }
20186         return [x,y];
20187     },
20188
20189     /**
20190      * Aligns this element with another element relative to the specified anchor points. If the other element is the
20191      * document it aligns it to the viewport.
20192      * The position parameter is optional, and can be specified in any one of the following formats:
20193      * <ul>
20194      *   <li><b>Blank</b>: Defaults to aligning the element's top-left corner to the target's bottom-left corner ("tl-bl").</li>
20195      *   <li><b>One anchor (deprecated)</b>: The passed anchor position is used as the target element's anchor point.
20196      *       The element being aligned will position its top-left corner (tl) to that point.  <i>This method has been
20197      *       deprecated in favor of the newer two anchor syntax below</i>.</li>
20198      *   <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
20199      *       element's anchor point, and the second value is used as the target's anchor point.</li>
20200      * </ul>
20201      * In addition to the anchor points, the position parameter also supports the "?" character.  If "?" is passed at the end of
20202      * the position string, the element will attempt to align as specified, but the position will be adjusted to constrain to
20203      * the viewport if necessary.  Note that the element being aligned might be swapped to align to a different position than
20204      * that specified in order to enforce the viewport constraints.
20205      * Following are all of the supported anchor positions:
20206 <pre>
20207 Value  Description
20208 -----  -----------------------------
20209 tl     The top left corner (default)
20210 t      The center of the top edge
20211 tr     The top right corner
20212 l      The center of the left edge
20213 c      In the center of the element
20214 r      The center of the right edge
20215 bl     The bottom left corner
20216 b      The center of the bottom edge
20217 br     The bottom right corner
20218 </pre>
20219 Example Usage:
20220 <pre><code>
20221 // align el to other-el using the default positioning ("tl-bl", non-constrained)
20222 el.alignTo("other-el");
20223
20224 // align the top left corner of el with the top right corner of other-el (constrained to viewport)
20225 el.alignTo("other-el", "tr?");
20226
20227 // align the bottom right corner of el with the center left edge of other-el
20228 el.alignTo("other-el", "br-l?");
20229
20230 // align the center of el with the bottom left corner of other-el and
20231 // adjust the x position by -6 pixels (and the y position by 0)
20232 el.alignTo("other-el", "c-bl", [-6, 0]);
20233 </code></pre>
20234      * @param {String/HTMLElement/Ext.Element} element The element to align to.
20235      * @param {String} [position="tl-bl?"] The position to align to
20236      * @param {Number[]} [offsets] Offset the positioning by [x, y]
20237      * @param {Boolean/Object} [animate] true for the default animation or a standard Element animation config object
20238      * @return {Ext.Element} this
20239      */
20240     alignTo : function(element, position, offsets, animate){
20241         var me = this;
20242         return me.setXY(me.getAlignToXY(element, position, offsets),
20243                         me.anim && !!animate ? me.anim(animate) : false);
20244     },
20245
20246     // private ==>  used outside of core
20247     adjustForConstraints : function(xy, parent) {
20248         var vector = this.getConstrainVector(parent, xy);
20249         if (vector) {
20250             xy[0] += vector[0];
20251             xy[1] += vector[1];
20252         }
20253         return xy;
20254     },
20255
20256     /**
20257      * <p>Returns the <code>[X, Y]</code> vector by which this element must be translated to make a best attempt
20258      * to constrain within the passed constraint. Returns <code>false</code> is this element does not need to be moved.</p>
20259      * <p>Priority is given to constraining the top and left within the constraint.</p>
20260      * <p>The constraint may either be an existing element into which this element is to be constrained, or
20261      * an {@link Ext.util.Region Region} into which this element is to be constrained.</p>
20262      * @param constrainTo {Mixed} The Element or {@link Ext.util.Region Region} into which this element is to be constrained.
20263      * @param proposedPosition {Array} A proposed <code>[X, Y]</code> position to test for validity and to produce a vector for instead
20264      * of using this Element's current position;
20265      * @returns {Number[]/Boolean} <b>If</b> this element <i>needs</i> to be translated, an <code>[X, Y]</code>
20266      * vector by which this element must be translated. Otherwise, <code>false</code>.
20267      */
20268     getConstrainVector: function(constrainTo, proposedPosition) {
20269         if (!(constrainTo instanceof Ext.util.Region)) {
20270             constrainTo = Ext.get(constrainTo).getViewRegion();
20271         }
20272         var thisRegion = this.getRegion(),
20273             vector = [0, 0],
20274             shadowSize = this.shadow && this.shadow.offset,
20275             overflowed = false;
20276
20277         // Shift this region to occupy the proposed position
20278         if (proposedPosition) {
20279             thisRegion.translateBy(proposedPosition[0] - thisRegion.x, proposedPosition[1] - thisRegion.y);
20280         }
20281
20282         // Reduce the constrain region to allow for shadow
20283         // TODO: Rewrite the Shadow class. When that's done, get the extra for each side from the Shadow.
20284         if (shadowSize) {
20285             constrainTo.adjust(0, -shadowSize, -shadowSize, shadowSize);
20286         }
20287
20288         // Constrain the X coordinate by however much this Element overflows
20289         if (thisRegion.right > constrainTo.right) {
20290             overflowed = true;
20291             vector[0] = (constrainTo.right - thisRegion.right);    // overflowed the right
20292         }
20293         if (thisRegion.left + vector[0] < constrainTo.left) {
20294             overflowed = true;
20295             vector[0] = (constrainTo.left - thisRegion.left);      // overflowed the left
20296         }
20297
20298         // Constrain the Y coordinate by however much this Element overflows
20299         if (thisRegion.bottom > constrainTo.bottom) {
20300             overflowed = true;
20301             vector[1] = (constrainTo.bottom - thisRegion.bottom);  // overflowed the bottom
20302         }
20303         if (thisRegion.top + vector[1] < constrainTo.top) {
20304             overflowed = true;
20305             vector[1] = (constrainTo.top - thisRegion.top);        // overflowed the top
20306         }
20307         return overflowed ? vector : false;
20308     },
20309
20310     /**
20311     * Calculates the x, y to center this element on the screen
20312     * @return {Number[]} The x, y values [x, y]
20313     */
20314     getCenterXY : function(){
20315         return this.getAlignToXY(document, 'c-c');
20316     },
20317
20318     /**
20319     * Centers the Element in either the viewport, or another Element.
20320     * @param {String/HTMLElement/Ext.Element} centerIn (optional) The element in which to center the element.
20321     */
20322     center : function(centerIn){
20323         return this.alignTo(centerIn || document, 'c-c');
20324     }
20325 });
20326
20327 /**
20328  * @class Ext.Element
20329  */
20330 (function(){
20331
20332 var ELEMENT = Ext.Element,
20333     LEFT = "left",
20334     RIGHT = "right",
20335     TOP = "top",
20336     BOTTOM = "bottom",
20337     POSITION = "position",
20338     STATIC = "static",
20339     RELATIVE = "relative",
20340     AUTO = "auto",
20341     ZINDEX = "z-index";
20342
20343 Ext.override(Ext.Element, {
20344     /**
20345       * 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).
20346       * @return {Number} The X position of the element
20347       */
20348     getX : function(){
20349         return ELEMENT.getX(this.dom);
20350     },
20351
20352     /**
20353       * 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).
20354       * @return {Number} The Y position of the element
20355       */
20356     getY : function(){
20357         return ELEMENT.getY(this.dom);
20358     },
20359
20360     /**
20361       * 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).
20362       * @return {Number[]} The XY position of the element
20363       */
20364     getXY : function(){
20365         return ELEMENT.getXY(this.dom);
20366     },
20367
20368     /**
20369       * 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.
20370       * @param {String/HTMLElement/Ext.Element} element The element to get the offsets from.
20371       * @return {Number[]} The XY page offsets (e.g. [100, -200])
20372       */
20373     getOffsetsTo : function(el){
20374         var o = this.getXY(),
20375             e = Ext.fly(el, '_internal').getXY();
20376         return [o[0]-e[0],o[1]-e[1]];
20377     },
20378
20379     /**
20380      * 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).
20381      * @param {Number} The X position of the element
20382      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
20383      * @return {Ext.Element} this
20384      */
20385     setX : function(x, animate){
20386         return this.setXY([x, this.getY()], animate);
20387     },
20388
20389     /**
20390      * 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).
20391      * @param {Number} The Y position of the element
20392      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
20393      * @return {Ext.Element} this
20394      */
20395     setY : function(y, animate){
20396         return this.setXY([this.getX(), y], animate);
20397     },
20398
20399     /**
20400      * Sets the element's left position directly using CSS style (instead of {@link #setX}).
20401      * @param {String} left The left CSS property value
20402      * @return {Ext.Element} this
20403      */
20404     setLeft : function(left){
20405         this.setStyle(LEFT, this.addUnits(left));
20406         return this;
20407     },
20408
20409     /**
20410      * Sets the element's top position directly using CSS style (instead of {@link #setY}).
20411      * @param {String} top The top CSS property value
20412      * @return {Ext.Element} this
20413      */
20414     setTop : function(top){
20415         this.setStyle(TOP, this.addUnits(top));
20416         return this;
20417     },
20418
20419     /**
20420      * Sets the element's CSS right style.
20421      * @param {String} right The right CSS property value
20422      * @return {Ext.Element} this
20423      */
20424     setRight : function(right){
20425         this.setStyle(RIGHT, this.addUnits(right));
20426         return this;
20427     },
20428
20429     /**
20430      * Sets the element's CSS bottom style.
20431      * @param {String} bottom The bottom CSS property value
20432      * @return {Ext.Element} this
20433      */
20434     setBottom : function(bottom){
20435         this.setStyle(BOTTOM, this.addUnits(bottom));
20436         return this;
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[]} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
20443      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
20444      * @return {Ext.Element} this
20445      */
20446     setXY: function(pos, animate) {
20447         var me = this;
20448         if (!animate || !me.anim) {
20449             ELEMENT.setXY(me.dom, pos);
20450         }
20451         else {
20452             if (!Ext.isObject(animate)) {
20453                 animate = {};
20454             }
20455             me.animate(Ext.applyIf({ to: { x: pos[0], y: pos[1] } }, animate));
20456         }
20457         return me;
20458     },
20459
20460     /**
20461      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
20462      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
20463      * @param {Number} x X value for new position (coordinates are page-based)
20464      * @param {Number} y Y value for new position (coordinates are page-based)
20465      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
20466      * @return {Ext.Element} this
20467      */
20468     setLocation : function(x, y, animate){
20469         return this.setXY([x, y], animate);
20470     },
20471
20472     /**
20473      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
20474      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
20475      * @param {Number} x X value for new position (coordinates are page-based)
20476      * @param {Number} y Y value for new position (coordinates are page-based)
20477      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
20478      * @return {Ext.Element} this
20479      */
20480     moveTo : function(x, y, animate){
20481         return this.setXY([x, y], animate);
20482     },
20483
20484     /**
20485      * Gets the left X coordinate
20486      * @param {Boolean} local True to get the local css position instead of page coordinate
20487      * @return {Number}
20488      */
20489     getLeft : function(local){
20490         return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
20491     },
20492
20493     /**
20494      * Gets the right X coordinate of the element (element X position + element width)
20495      * @param {Boolean} local True to get the local css position instead of page coordinate
20496      * @return {Number}
20497      */
20498     getRight : function(local){
20499         var me = this;
20500         return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
20501     },
20502
20503     /**
20504      * Gets the top Y coordinate
20505      * @param {Boolean} local True to get the local css position instead of page coordinate
20506      * @return {Number}
20507      */
20508     getTop : function(local) {
20509         return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
20510     },
20511
20512     /**
20513      * Gets the bottom Y coordinate of the element (element Y position + element height)
20514      * @param {Boolean} local True to get the local css position instead of page coordinate
20515      * @return {Number}
20516      */
20517     getBottom : function(local){
20518         var me = this;
20519         return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
20520     },
20521
20522     /**
20523     * Initializes positioning on this element. If a desired position is not passed, it will make the
20524     * the element positioned relative IF it is not already positioned.
20525     * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
20526     * @param {Number} zIndex (optional) The zIndex to apply
20527     * @param {Number} x (optional) Set the page X position
20528     * @param {Number} y (optional) Set the page Y position
20529     */
20530     position : function(pos, zIndex, x, y) {
20531         var me = this;
20532
20533         if (!pos && me.isStyle(POSITION, STATIC)){
20534             me.setStyle(POSITION, RELATIVE);
20535         } else if(pos) {
20536             me.setStyle(POSITION, pos);
20537         }
20538         if (zIndex){
20539             me.setStyle(ZINDEX, zIndex);
20540         }
20541         if (x || y) {
20542             me.setXY([x || false, y || false]);
20543         }
20544     },
20545
20546     /**
20547     * Clear positioning back to the default when the document was loaded
20548     * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
20549     * @return {Ext.Element} this
20550      */
20551     clearPositioning : function(value){
20552         value = value || '';
20553         this.setStyle({
20554             left : value,
20555             right : value,
20556             top : value,
20557             bottom : value,
20558             "z-index" : "",
20559             position : STATIC
20560         });
20561         return this;
20562     },
20563
20564     /**
20565     * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
20566     * snapshot before performing an update and then restoring the element.
20567     * @return {Object}
20568     */
20569     getPositioning : function(){
20570         var l = this.getStyle(LEFT);
20571         var t = this.getStyle(TOP);
20572         return {
20573             "position" : this.getStyle(POSITION),
20574             "left" : l,
20575             "right" : l ? "" : this.getStyle(RIGHT),
20576             "top" : t,
20577             "bottom" : t ? "" : this.getStyle(BOTTOM),
20578             "z-index" : this.getStyle(ZINDEX)
20579         };
20580     },
20581
20582     /**
20583     * Set positioning with an object returned by getPositioning().
20584     * @param {Object} posCfg
20585     * @return {Ext.Element} this
20586      */
20587     setPositioning : function(pc){
20588         var me = this,
20589             style = me.dom.style;
20590
20591         me.setStyle(pc);
20592
20593         if(pc.right == AUTO){
20594             style.right = "";
20595         }
20596         if(pc.bottom == AUTO){
20597             style.bottom = "";
20598         }
20599
20600         return me;
20601     },
20602
20603     /**
20604      * Translates the passed page coordinates into left/top css values for this element
20605      * @param {Number/Number[]} x The page x or an array containing [x, y]
20606      * @param {Number} y (optional) The page y, required if x is not an array
20607      * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
20608      */
20609     translatePoints: function(x, y) {
20610         if (Ext.isArray(x)) {
20611              y = x[1];
20612              x = x[0];
20613         }
20614         var me = this,
20615             relative = me.isStyle(POSITION, RELATIVE),
20616             o = me.getXY(),
20617             left = parseInt(me.getStyle(LEFT), 10),
20618             top = parseInt(me.getStyle(TOP), 10);
20619
20620         if (!Ext.isNumber(left)) {
20621             left = relative ? 0 : me.dom.offsetLeft;
20622         }
20623         if (!Ext.isNumber(top)) {
20624             top = relative ? 0 : me.dom.offsetTop;
20625         }
20626         left = (Ext.isNumber(x)) ? x - o[0] + left : undefined;
20627         top = (Ext.isNumber(y)) ? y - o[1] + top : undefined;
20628         return {
20629             left: left,
20630             top: top
20631         };
20632     },
20633
20634     /**
20635      * 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.
20636      * @param {Object} box The box to fill {x, y, width, height}
20637      * @param {Boolean} adjust (optional) Whether to adjust for box-model issues automatically
20638      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20639      * @return {Ext.Element} this
20640      */
20641     setBox: function(box, adjust, animate) {
20642         var me = this,
20643             w = box.width,
20644             h = box.height;
20645         if ((adjust && !me.autoBoxAdjust) && !me.isBorderBox()) {
20646             w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
20647             h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
20648         }
20649         me.setBounds(box.x, box.y, w, h, animate);
20650         return me;
20651     },
20652
20653     /**
20654      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
20655      * set another Element's size/location to match this element.
20656      * @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
20657      * @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
20658      * @return {Object} box An object in the format<pre><code>
20659 {
20660     x: &lt;Element's X position>,
20661     y: &lt;Element's Y position>,
20662     width: &lt;Element's width>,
20663     height: &lt;Element's height>,
20664     bottom: &lt;Element's lower bound>,
20665     right: &lt;Element's rightmost bound>
20666 }
20667 </code></pre>
20668      * The returned object may also be addressed as an Array where index 0 contains the X position
20669      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
20670      */
20671     getBox: function(contentBox, local) {
20672         var me = this,
20673             xy,
20674             left,
20675             top,
20676             getBorderWidth = me.getBorderWidth,
20677             getPadding = me.getPadding,
20678             l, r, t, b, w, h, bx;
20679         if (!local) {
20680             xy = me.getXY();
20681         } else {
20682             left = parseInt(me.getStyle("left"), 10) || 0;
20683             top = parseInt(me.getStyle("top"), 10) || 0;
20684             xy = [left, top];
20685         }
20686         w = me.getWidth();
20687         h = me.getHeight();
20688         if (!contentBox) {
20689             bx = {
20690                 x: xy[0],
20691                 y: xy[1],
20692                 0: xy[0],
20693                 1: xy[1],
20694                 width: w,
20695                 height: h
20696             };
20697         } else {
20698             l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
20699             r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
20700             t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
20701             b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
20702             bx = {
20703                 x: xy[0] + l,
20704                 y: xy[1] + t,
20705                 0: xy[0] + l,
20706                 1: xy[1] + t,
20707                 width: w - (l + r),
20708                 height: h - (t + b)
20709             };
20710         }
20711         bx.right = bx.x + bx.width;
20712         bx.bottom = bx.y + bx.height;
20713         return bx;
20714     },
20715
20716     /**
20717      * Move this element relative to its current position.
20718      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
20719      * @param {Number} distance How far to move the element in pixels
20720      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20721      */
20722     move: function(direction, distance, animate) {
20723         var me = this,
20724             xy = me.getXY(),
20725             x = xy[0],
20726             y = xy[1],
20727             left = [x - distance, y],
20728             right = [x + distance, y],
20729             top = [x, y - distance],
20730             bottom = [x, y + distance],
20731             hash = {
20732                 l: left,
20733                 left: left,
20734                 r: right,
20735                 right: right,
20736                 t: top,
20737                 top: top,
20738                 up: top,
20739                 b: bottom,
20740                 bottom: bottom,
20741                 down: bottom
20742             };
20743
20744         direction = direction.toLowerCase();
20745         me.moveTo(hash[direction][0], hash[direction][1], animate);
20746     },
20747
20748     /**
20749      * Quick set left and top adding default units
20750      * @param {String} left The left CSS property value
20751      * @param {String} top The top CSS property value
20752      * @return {Ext.Element} this
20753      */
20754     setLeftTop: function(left, top) {
20755         var me = this,
20756             style = me.dom.style;
20757         style.left = me.addUnits(left);
20758         style.top = me.addUnits(top);
20759         return me;
20760     },
20761
20762     /**
20763      * Returns the region of this element.
20764      * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
20765      * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data.
20766      */
20767     getRegion: function() {
20768         return this.getPageBox(true);
20769     },
20770
20771     /**
20772      * Returns the <b>content</b> region of this element. That is the region within the borders and padding.
20773      * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data.
20774      */
20775     getViewRegion: function() {
20776         var me = this,
20777             isBody = me.dom === document.body,
20778             scroll, pos, top, left, width, height;
20779
20780         // For the body we want to do some special logic
20781         if (isBody) {
20782             scroll = me.getScroll();
20783             left = scroll.left;
20784             top = scroll.top;
20785             width = Ext.Element.getViewportWidth();
20786             height = Ext.Element.getViewportHeight();
20787         }
20788         else {
20789             pos = me.getXY();
20790             left = pos[0] + me.getBorderWidth('l') + me.getPadding('l');
20791             top = pos[1] + me.getBorderWidth('t') + me.getPadding('t');
20792             width = me.getWidth(true);
20793             height = me.getHeight(true);
20794         }
20795
20796         return Ext.create('Ext.util.Region', top, left + width, top + height, left);
20797     },
20798
20799     /**
20800      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
20801      * set another Element's size/location to match this element.
20802      * @param {Boolean} asRegion(optional) If true an Ext.util.Region will be returned
20803      * @return {Object} box An object in the format<pre><code>
20804 {
20805     x: &lt;Element's X position>,
20806     y: &lt;Element's Y position>,
20807     width: &lt;Element's width>,
20808     height: &lt;Element's height>,
20809     bottom: &lt;Element's lower bound>,
20810     right: &lt;Element's rightmost bound>
20811 }
20812 </code></pre>
20813      * The returned object may also be addressed as an Array where index 0 contains the X position
20814      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
20815      */
20816     getPageBox : function(getRegion) {
20817         var me = this,
20818             el = me.dom,
20819             isDoc = el === document.body,
20820             w = isDoc ? Ext.Element.getViewWidth()  : el.offsetWidth,
20821             h = isDoc ? Ext.Element.getViewHeight() : el.offsetHeight,
20822             xy = me.getXY(),
20823             t = xy[1],
20824             r = xy[0] + w,
20825             b = xy[1] + h,
20826             l = xy[0];
20827
20828         if (getRegion) {
20829             return Ext.create('Ext.util.Region', t, r, b, l);
20830         }
20831         else {
20832             return {
20833                 left: l,
20834                 top: t,
20835                 width: w,
20836                 height: h,
20837                 right: r,
20838                 bottom: b
20839             };
20840         }
20841     },
20842
20843     /**
20844      * Sets the element's position and size in one shot. If animation is true then width, height, x and y will be animated concurrently.
20845      * @param {Number} x X value for new position (coordinates are page-based)
20846      * @param {Number} y Y value for new position (coordinates are page-based)
20847      * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
20848      * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
20849      * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
20850      * </ul></div>
20851      * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
20852      * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
20853      * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
20854      * </ul></div>
20855      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20856      * @return {Ext.Element} this
20857      */
20858     setBounds: function(x, y, width, height, animate) {
20859         var me = this;
20860         if (!animate || !me.anim) {
20861             me.setSize(width, height);
20862             me.setLocation(x, y);
20863         } else {
20864             if (!Ext.isObject(animate)) {
20865                 animate = {};
20866             }
20867             me.animate(Ext.applyIf({
20868                 to: {
20869                     x: x,
20870                     y: y,
20871                     width: me.adjustWidth(width),
20872                     height: me.adjustHeight(height)
20873                 }
20874             }, animate));
20875         }
20876         return me;
20877     },
20878
20879     /**
20880      * Sets the element's position and size the specified region. If animation is true then width, height, x and y will be animated concurrently.
20881      * @param {Ext.util.Region} region The region to fill
20882      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20883      * @return {Ext.Element} this
20884      */
20885     setRegion: function(region, animate) {
20886         return this.setBounds(region.left, region.top, region.right - region.left, region.bottom - region.top, animate);
20887     }
20888 });
20889 })();
20890
20891 /**
20892  * @class Ext.Element
20893  */
20894 Ext.override(Ext.Element, {
20895     /**
20896      * Returns true if this element is scrollable.
20897      * @return {Boolean}
20898      */
20899     isScrollable : function(){
20900         var dom = this.dom;
20901         return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
20902     },
20903
20904     /**
20905      * Returns the current scroll position of the element.
20906      * @return {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
20907      */
20908     getScroll : function() {
20909         var d = this.dom, 
20910             doc = document,
20911             body = doc.body,
20912             docElement = doc.documentElement,
20913             l,
20914             t,
20915             ret;
20916
20917         if (d == doc || d == body) {
20918             if (Ext.isIE && Ext.isStrict) {
20919                 l = docElement.scrollLeft; 
20920                 t = docElement.scrollTop;
20921             } else {
20922                 l = window.pageXOffset;
20923                 t = window.pageYOffset;
20924             }
20925             ret = {
20926                 left: l || (body ? body.scrollLeft : 0), 
20927                 top : t || (body ? body.scrollTop : 0)
20928             };
20929         } else {
20930             ret = {
20931                 left: d.scrollLeft, 
20932                 top : d.scrollTop
20933             };
20934         }
20935         
20936         return ret;
20937     },
20938     
20939     /**
20940      * 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().
20941      * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
20942      * @param {Number} value The new scroll value
20943      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20944      * @return {Ext.Element} this
20945      */
20946     scrollTo : function(side, value, animate) {
20947         //check if we're scrolling top or left
20948         var top = /top/i.test(side),
20949             me = this,
20950             dom = me.dom,
20951             obj = {},
20952             prop;
20953         if (!animate || !me.anim) {
20954             // just setting the value, so grab the direction
20955             prop = 'scroll' + (top ? 'Top' : 'Left');
20956             dom[prop] = value;
20957         }
20958         else {
20959             if (!Ext.isObject(animate)) {
20960                 animate = {};
20961             }
20962             obj['scroll' + (top ? 'Top' : 'Left')] = value;
20963             me.animate(Ext.applyIf({
20964                 to: obj
20965             }, animate));
20966         }
20967         return me;
20968     },
20969
20970     /**
20971      * Scrolls this element into view within the passed container.
20972      * @param {String/HTMLElement/Ext.Element} container (optional) The container element to scroll (defaults to document.body).  Should be a
20973      * string (id), dom node, or Ext.Element.
20974      * @param {Boolean} hscroll (optional) False to disable horizontal scroll (defaults to true)
20975      * @return {Ext.Element} this
20976      */
20977     scrollIntoView : function(container, hscroll) {
20978         container = Ext.getDom(container) || Ext.getBody().dom;
20979         var el = this.dom,
20980             offsets = this.getOffsetsTo(container),
20981             // el's box
20982             left = offsets[0] + container.scrollLeft,
20983             top = offsets[1] + container.scrollTop,
20984             bottom = top + el.offsetHeight,
20985             right = left + el.offsetWidth,
20986             // ct's box
20987             ctClientHeight = container.clientHeight,
20988             ctScrollTop = parseInt(container.scrollTop, 10),
20989             ctScrollLeft = parseInt(container.scrollLeft, 10),
20990             ctBottom = ctScrollTop + ctClientHeight,
20991             ctRight = ctScrollLeft + container.clientWidth;
20992
20993         if (el.offsetHeight > ctClientHeight || top < ctScrollTop) {
20994             container.scrollTop = top;
20995         } else if (bottom > ctBottom) {
20996             container.scrollTop = bottom - ctClientHeight;
20997         }
20998         // corrects IE, other browsers will ignore
20999         container.scrollTop = container.scrollTop;
21000
21001         if (hscroll !== false) {
21002             if (el.offsetWidth > container.clientWidth || left < ctScrollLeft) {
21003                 container.scrollLeft = left;
21004             }
21005             else if (right > ctRight) {
21006                 container.scrollLeft = right - container.clientWidth;
21007             }
21008             container.scrollLeft = container.scrollLeft;
21009         }
21010         return this;
21011     },
21012
21013     // private
21014     scrollChildIntoView : function(child, hscroll) {
21015         Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
21016     },
21017
21018     /**
21019      * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
21020      * within this element's scrollable range.
21021      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
21022      * @param {Number} distance How far to scroll the element in pixels
21023      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
21024      * @return {Boolean} Returns true if a scroll was triggered or false if the element
21025      * was scrolled as far as it could go.
21026      */
21027      scroll : function(direction, distance, animate) {
21028         if (!this.isScrollable()) {
21029             return false;
21030         }
21031         var el = this.dom,
21032             l = el.scrollLeft, t = el.scrollTop,
21033             w = el.scrollWidth, h = el.scrollHeight,
21034             cw = el.clientWidth, ch = el.clientHeight,
21035             scrolled = false, v,
21036             hash = {
21037                 l: Math.min(l + distance, w-cw),
21038                 r: v = Math.max(l - distance, 0),
21039                 t: Math.max(t - distance, 0),
21040                 b: Math.min(t + distance, h-ch)
21041             };
21042             hash.d = hash.b;
21043             hash.u = hash.t;
21044
21045         direction = direction.substr(0, 1);
21046         if ((v = hash[direction]) > -1) {
21047             scrolled = true;
21048             this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.anim(animate));
21049         }
21050         return scrolled;
21051     }
21052 });
21053 /**
21054  * @class Ext.Element
21055  */
21056 Ext.Element.addMethods(
21057     function() {
21058         var VISIBILITY      = "visibility",
21059             DISPLAY         = "display",
21060             HIDDEN          = "hidden",
21061             NONE            = "none",
21062             XMASKED         = Ext.baseCSSPrefix + "masked",
21063             XMASKEDRELATIVE = Ext.baseCSSPrefix + "masked-relative",
21064             data            = Ext.Element.data;
21065
21066         return {
21067             /**
21068              * Checks whether the element is currently visible using both visibility and display properties.
21069              * @param {Boolean} [deep=false] True to walk the dom and see if parent elements are hidden
21070              * @return {Boolean} True if the element is currently visible, else false
21071              */
21072             isVisible : function(deep) {
21073                 var vis = !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE),
21074                     p   = this.dom.parentNode;
21075
21076                 if (deep !== true || !vis) {
21077                     return vis;
21078                 }
21079
21080                 while (p && !(/^body/i.test(p.tagName))) {
21081                     if (!Ext.fly(p, '_isVisible').isVisible()) {
21082                         return false;
21083                     }
21084                     p = p.parentNode;
21085                 }
21086                 return true;
21087             },
21088
21089             /**
21090              * Returns true if display is not "none"
21091              * @return {Boolean}
21092              */
21093             isDisplayed : function() {
21094                 return !this.isStyle(DISPLAY, NONE);
21095             },
21096
21097             /**
21098              * Convenience method for setVisibilityMode(Element.DISPLAY)
21099              * @param {String} display (optional) What to set display to when visible
21100              * @return {Ext.Element} this
21101              */
21102             enableDisplayMode : function(display) {
21103                 this.setVisibilityMode(Ext.Element.DISPLAY);
21104
21105                 if (!Ext.isEmpty(display)) {
21106                     data(this.dom, 'originalDisplay', display);
21107                 }
21108
21109                 return this;
21110             },
21111
21112             /**
21113              * Puts a mask over this element to disable user interaction. Requires core.css.
21114              * This method can only be applied to elements which accept child nodes.
21115              * @param {String} msg (optional) A message to display in the mask
21116              * @param {String} msgCls (optional) A css class to apply to the msg element
21117              * @return {Ext.Element} The mask element
21118              */
21119             mask : function(msg, msgCls) {
21120                 var me  = this,
21121                     dom = me.dom,
21122                     setExpression = dom.style.setExpression,
21123                     dh  = Ext.DomHelper,
21124                     EXTELMASKMSG = Ext.baseCSSPrefix + "mask-msg",
21125                     el,
21126                     mask;
21127
21128                 if (!(/^body/i.test(dom.tagName) && me.getStyle('position') == 'static')) {
21129                     me.addCls(XMASKEDRELATIVE);
21130                 }
21131                 el = data(dom, 'maskMsg');
21132                 if (el) {
21133                     el.remove();
21134                 }
21135                 el = data(dom, 'mask');
21136                 if (el) {
21137                     el.remove();
21138                 }
21139
21140                 mask = dh.append(dom, {cls : Ext.baseCSSPrefix + "mask"}, true);
21141                 data(dom, 'mask', mask);
21142
21143                 me.addCls(XMASKED);
21144                 mask.setDisplayed(true);
21145
21146                 if (typeof msg == 'string') {
21147                     var mm = dh.append(dom, {cls : EXTELMASKMSG, cn:{tag:'div'}}, true);
21148                     data(dom, 'maskMsg', mm);
21149                     mm.dom.className = msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG;
21150                     mm.dom.firstChild.innerHTML = msg;
21151                     mm.setDisplayed(true);
21152                     mm.center(me);
21153                 }
21154                 // NOTE: CSS expressions are resource intensive and to be used only as a last resort
21155                 // These expressions are removed as soon as they are no longer necessary - in the unmask method.
21156                 // In normal use cases an element will be masked for a limited period of time.
21157                 // Fix for https://sencha.jira.com/browse/EXTJSIV-19.
21158                 // IE6 strict mode and IE6-9 quirks mode takes off left+right padding when calculating width!
21159                 if (!Ext.supports.IncludePaddingInWidthCalculation && setExpression) {
21160                     mask.dom.style.setExpression('width', 'this.parentNode.offsetWidth + "px"');
21161                 }
21162
21163                 // Some versions and modes of IE subtract top+bottom padding when calculating height.
21164                 // Different versions from those which make the same error for width!
21165                 if (!Ext.supports.IncludePaddingInHeightCalculation && setExpression) {
21166                     mask.dom.style.setExpression('height', 'this.parentNode.offsetHeight + "px"');
21167                 }
21168                 // ie will not expand full height automatically
21169                 else if (Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto') {
21170                     mask.setSize(undefined, me.getHeight());
21171                 }
21172                 return mask;
21173             },
21174
21175             /**
21176              * Removes a previously applied mask.
21177              */
21178             unmask : function() {
21179                 var me      = this,
21180                     dom     = me.dom,
21181                     mask    = data(dom, 'mask'),
21182                     maskMsg = data(dom, 'maskMsg');
21183
21184                 if (mask) {
21185                     // Remove resource-intensive CSS expressions as soon as they are not required.
21186                     if (mask.dom.style.clearExpression) {
21187                         mask.dom.style.clearExpression('width');
21188                         mask.dom.style.clearExpression('height');
21189                     }
21190                     if (maskMsg) {
21191                         maskMsg.remove();
21192                         data(dom, 'maskMsg', undefined);
21193                     }
21194
21195                     mask.remove();
21196                     data(dom, 'mask', undefined);
21197                     me.removeCls([XMASKED, XMASKEDRELATIVE]);
21198                 }
21199             },
21200             /**
21201              * Returns true if this element is masked. Also re-centers any displayed message within the mask.
21202              * @return {Boolean}
21203              */
21204             isMasked : function() {
21205                 var me = this,
21206                     mask = data(me.dom, 'mask'),
21207                     maskMsg = data(me.dom, 'maskMsg');
21208
21209                 if (mask && mask.isVisible()) {
21210                     if (maskMsg) {
21211                         maskMsg.center(me);
21212                     }
21213                     return true;
21214                 }
21215                 return false;
21216             },
21217
21218             /**
21219              * Creates an iframe shim for this element to keep selects and other windowed objects from
21220              * showing through.
21221              * @return {Ext.Element} The new shim element
21222              */
21223             createShim : function() {
21224                 var el = document.createElement('iframe'),
21225                     shim;
21226
21227                 el.frameBorder = '0';
21228                 el.className = Ext.baseCSSPrefix + 'shim';
21229                 el.src = Ext.SSL_SECURE_URL;
21230                 shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
21231                 shim.autoBoxAdjust = false;
21232                 return shim;
21233             }
21234         };
21235     }()
21236 );
21237 /**
21238  * @class Ext.Element
21239  */
21240 Ext.Element.addMethods({
21241     /**
21242      * Convenience method for constructing a KeyMap
21243      * @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:
21244      * <code>{key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}</code>
21245      * @param {Function} fn The function to call
21246      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the specified function is executed. Defaults to this Element.
21247      * @return {Ext.util.KeyMap} The KeyMap created
21248      */
21249     addKeyListener : function(key, fn, scope){
21250         var config;
21251         if(typeof key != 'object' || Ext.isArray(key)){
21252             config = {
21253                 key: key,
21254                 fn: fn,
21255                 scope: scope
21256             };
21257         }else{
21258             config = {
21259                 key : key.key,
21260                 shift : key.shift,
21261                 ctrl : key.ctrl,
21262                 alt : key.alt,
21263                 fn: fn,
21264                 scope: scope
21265             };
21266         }
21267         return Ext.create('Ext.util.KeyMap', this, config);
21268     },
21269
21270     /**
21271      * Creates a KeyMap for this element
21272      * @param {Object} config The KeyMap config. See {@link Ext.util.KeyMap} for more details
21273      * @return {Ext.util.KeyMap} The KeyMap created
21274      */
21275     addKeyMap : function(config){
21276         return Ext.create('Ext.util.KeyMap', this, config);
21277     }
21278 });
21279
21280 //Import the newly-added Ext.Element functions into CompositeElementLite. We call this here because
21281 //Element.keys.js is the last extra Ext.Element include in the ext-all.js build
21282 Ext.CompositeElementLite.importElementMethods();
21283
21284 /**
21285  * @class Ext.CompositeElementLite
21286  */
21287 Ext.apply(Ext.CompositeElementLite.prototype, {
21288     addElements : function(els, root){
21289         if(!els){
21290             return this;
21291         }
21292         if(typeof els == "string"){
21293             els = Ext.Element.selectorFunction(els, root);
21294         }
21295         var yels = this.elements;
21296         Ext.each(els, function(e) {
21297             yels.push(Ext.get(e));
21298         });
21299         return this;
21300     },
21301
21302     /**
21303      * Returns the first Element
21304      * @return {Ext.Element}
21305      */
21306     first : function(){
21307         return this.item(0);
21308     },
21309
21310     /**
21311      * Returns the last Element
21312      * @return {Ext.Element}
21313      */
21314     last : function(){
21315         return this.item(this.getCount()-1);
21316     },
21317
21318     /**
21319      * Returns true if this composite contains the passed element
21320      * @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.
21321      * @return Boolean
21322      */
21323     contains : function(el){
21324         return this.indexOf(el) != -1;
21325     },
21326
21327     /**
21328     * Removes the specified element(s).
21329     * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the element in this composite
21330     * or an array of any of those.
21331     * @param {Boolean} removeDom (optional) True to also remove the element from the document
21332     * @return {Ext.CompositeElement} this
21333     */
21334     removeElement : function(keys, removeDom){
21335         var me = this,
21336             els = this.elements,
21337             el;
21338         Ext.each(keys, function(val){
21339             if ((el = (els[val] || els[val = me.indexOf(val)]))) {
21340                 if(removeDom){
21341                     if(el.dom){
21342                         el.remove();
21343                     }else{
21344                         Ext.removeNode(el);
21345                     }
21346                 }
21347                 Ext.Array.erase(els, val, 1);
21348             }
21349         });
21350         return this;
21351     }
21352 });
21353
21354 /**
21355  * @class Ext.CompositeElement
21356  * @extends Ext.CompositeElementLite
21357  * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
21358  * members, or to perform collective actions upon the whole set.</p>
21359  * <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
21360  * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
21361  * <p>All methods return <i>this</i> and can be chained.</p>
21362  * Usage:
21363 <pre><code>
21364 var els = Ext.select("#some-el div.some-class", true);
21365 // or select directly from an existing element
21366 var el = Ext.get('some-el');
21367 el.select('div.some-class', true);
21368
21369 els.setWidth(100); // all elements become 100 width
21370 els.hide(true); // all elements fade out and hide
21371 // or
21372 els.setWidth(100).hide(true);
21373 </code></pre>
21374  */
21375 Ext.CompositeElement = Ext.extend(Ext.CompositeElementLite, {
21376
21377     constructor : function(els, root){
21378         this.elements = [];
21379         this.add(els, root);
21380     },
21381
21382     // private
21383     getElement : function(el){
21384         // In this case just return it, since we already have a reference to it
21385         return el;
21386     },
21387
21388     // private
21389     transformElement : function(el){
21390         return Ext.get(el);
21391     }
21392 });
21393
21394 /**
21395  * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
21396  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
21397  * {@link Ext.CompositeElementLite CompositeElementLite} object.
21398  * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
21399  * @param {Boolean} [unique] true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
21400  * @param {HTMLElement/String} [root] The root element of the query or id of the root
21401  * @return {Ext.CompositeElementLite/Ext.CompositeElement}
21402  * @member Ext.Element
21403  * @method select
21404  */
21405 Ext.Element.select = function(selector, unique, root){
21406     var els;
21407     if(typeof selector == "string"){
21408         els = Ext.Element.selectorFunction(selector, root);
21409     }else if(selector.length !== undefined){
21410         els = selector;
21411     }else{
21412         Ext.Error.raise({
21413             sourceClass: "Ext.Element",
21414             sourceMethod: "select",
21415             selector: selector,
21416             unique: unique,
21417             root: root,
21418             msg: "Invalid selector specified: " + selector
21419         });
21420     }
21421     return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
21422 };
21423
21424 /**
21425  * Shorthand of {@link Ext.Element#select}.
21426  * @member Ext
21427  * @method select
21428  * @alias Ext.Element#select
21429  */
21430 Ext.select = Ext.Element.select;
21431
21432
21433 /*
21434
21435 This file is part of Ext JS 4
21436
21437 Copyright (c) 2011 Sencha Inc
21438
21439 Contact:  http://www.sencha.com/contact
21440
21441 GNU General Public License Usage
21442 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.
21443
21444 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
21445
21446 */
21447 /**
21448  * Base class that provides a common interface for publishing events. Subclasses are expected to to have a property
21449  * "events" with all the events defined, and, optionally, a property "listeners" with configured listeners defined.
21450  *
21451  * For example:
21452  *
21453  *     Ext.define('Employee', {
21454  *         extend: 'Ext.util.Observable',
21455  *         constructor: function(config){
21456  *             this.name = config.name;
21457  *             this.addEvents({
21458  *                 "fired" : true,
21459  *                 "quit" : true
21460  *             });
21461  *
21462  *             // Copy configured listeners into *this* object so that the base class's
21463  *             // constructor will add them.
21464  *             this.listeners = config.listeners;
21465  *
21466  *             // Call our superclass constructor to complete construction process.
21467  *             this.callParent(arguments)
21468  *         }
21469  *     });
21470  *
21471  * This could then be used like this:
21472  *
21473  *     var newEmployee = new Employee({
21474  *         name: employeeName,
21475  *         listeners: {
21476  *             quit: function() {
21477  *                 // By default, "this" will be the object that fired the event.
21478  *                 alert(this.name + " has quit!");
21479  *             }
21480  *         }
21481  *     });
21482  */
21483 Ext.define('Ext.util.Observable', {
21484
21485     /* Begin Definitions */
21486
21487     requires: ['Ext.util.Event'],
21488
21489     statics: {
21490         /**
21491          * Removes **all** added captures from the Observable.
21492          *
21493          * @param {Ext.util.Observable} o The Observable to release
21494          * @static
21495          */
21496         releaseCapture: function(o) {
21497             o.fireEvent = this.prototype.fireEvent;
21498         },
21499
21500         /**
21501          * Starts capture on the specified Observable. All events will be passed to the supplied function with the event
21502          * name + standard signature of the event **before** the event is fired. If the supplied function returns false,
21503          * the event will not fire.
21504          *
21505          * @param {Ext.util.Observable} o The Observable to capture events from.
21506          * @param {Function} fn The function to call when an event is fired.
21507          * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to
21508          * the Observable firing the event.
21509          * @static
21510          */
21511         capture: function(o, fn, scope) {
21512             o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
21513         },
21514
21515         /**
21516          * Sets observability on the passed class constructor.
21517          *
21518          * This makes any event fired on any instance of the passed class also fire a single event through
21519          * the **class** allowing for central handling of events on many instances at once.
21520          *
21521          * Usage:
21522          *
21523          *     Ext.util.Observable.observe(Ext.data.Connection);
21524          *     Ext.data.Connection.on('beforerequest', function(con, options) {
21525          *         console.log('Ajax request made to ' + options.url);
21526          *     });
21527          *
21528          * @param {Function} c The class constructor to make observable.
21529          * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
21530          * @static
21531          */
21532         observe: function(cls, listeners) {
21533             if (cls) {
21534                 if (!cls.isObservable) {
21535                     Ext.applyIf(cls, new this());
21536                     this.capture(cls.prototype, cls.fireEvent, cls);
21537                 }
21538                 if (Ext.isObject(listeners)) {
21539                     cls.on(listeners);
21540                 }
21541                 return cls;
21542             }
21543         }
21544     },
21545
21546     /* End Definitions */
21547
21548     /**
21549      * @cfg {Object} listeners
21550      *
21551      * A config object containing one or more event handlers to be added to this object during initialization. This
21552      * should be a valid listeners config object as specified in the {@link #addListener} example for attaching multiple
21553      * handlers at once.
21554      *
21555      * **DOM events from Ext JS {@link Ext.Component Components}**
21556      *
21557      * While _some_ Ext JS Component classes export selected DOM events (e.g. "click", "mouseover" etc), this is usually
21558      * only done when extra value can be added. For example the {@link Ext.view.View DataView}'s **`{@link
21559      * Ext.view.View#itemclick itemclick}`** event passing the node clicked on. To access DOM events directly from a
21560      * child element of a Component, we need to specify the `element` option to identify the Component property to add a
21561      * DOM listener to:
21562      *
21563      *     new Ext.panel.Panel({
21564      *         width: 400,
21565      *         height: 200,
21566      *         dockedItems: [{
21567      *             xtype: 'toolbar'
21568      *         }],
21569      *         listeners: {
21570      *             click: {
21571      *                 element: 'el', //bind to the underlying el property on the panel
21572      *                 fn: function(){ console.log('click el'); }
21573      *             },
21574      *             dblclick: {
21575      *                 element: 'body', //bind to the underlying body property on the panel
21576      *                 fn: function(){ console.log('dblclick body'); }
21577      *             }
21578      *         }
21579      *     });
21580      */
21581     // @private
21582     isObservable: true,
21583
21584     constructor: function(config) {
21585         var me = this;
21586
21587         Ext.apply(me, config);
21588         if (me.listeners) {
21589             me.on(me.listeners);
21590             delete me.listeners;
21591         }
21592         me.events = me.events || {};
21593
21594         if (me.bubbleEvents) {
21595             me.enableBubble(me.bubbleEvents);
21596         }
21597     },
21598
21599     // @private
21600     eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal|freezeEvent)$/,
21601
21602     /**
21603      * Adds listeners to any Observable object (or Ext.Element) which are automatically removed when this Component is
21604      * destroyed.
21605      *
21606      * @param {Ext.util.Observable/Ext.Element} item The item to which to add a listener/listeners.
21607      * @param {Object/String} ename The event name, or an object containing event name properties.
21608      * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
21609      * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
21610      * in which the handler function is executed.
21611      * @param {Object} opt (optional) If the `ename` parameter was an event name, this is the
21612      * {@link Ext.util.Observable#addListener addListener} options.
21613      */
21614     addManagedListener : function(item, ename, fn, scope, options) {
21615         var me = this,
21616             managedListeners = me.managedListeners = me.managedListeners || [],
21617             config;
21618
21619         if (typeof ename !== 'string') {
21620             options = ename;
21621             for (ename in options) {
21622                 if (options.hasOwnProperty(ename)) {
21623                     config = options[ename];
21624                     if (!me.eventOptionsRe.test(ename)) {
21625                         me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
21626                     }
21627                 }
21628             }
21629         }
21630         else {
21631             managedListeners.push({
21632                 item: item,
21633                 ename: ename,
21634                 fn: fn,
21635                 scope: scope,
21636                 options: options
21637             });
21638
21639             item.on(ename, fn, scope, options);
21640         }
21641     },
21642
21643     /**
21644      * Removes listeners that were added by the {@link #mon} method.
21645      *
21646      * @param {Ext.util.Observable/Ext.Element} item The item from which to remove a listener/listeners.
21647      * @param {Object/String} ename The event name, or an object containing event name properties.
21648      * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
21649      * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
21650      * in which the handler function is executed.
21651      */
21652     removeManagedListener : function(item, ename, fn, scope) {
21653         var me = this,
21654             options,
21655             config,
21656             managedListeners,
21657             length,
21658             i;
21659
21660         if (typeof ename !== 'string') {
21661             options = ename;
21662             for (ename in options) {
21663                 if (options.hasOwnProperty(ename)) {
21664                     config = options[ename];
21665                     if (!me.eventOptionsRe.test(ename)) {
21666                         me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
21667                     }
21668                 }
21669             }
21670         }
21671
21672         managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
21673
21674         for (i = 0, length = managedListeners.length; i < length; i++) {
21675             me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn, scope);
21676         }
21677     },
21678
21679     /**
21680      * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
21681      * to {@link #addListener}).
21682      *
21683      * An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) by
21684      * calling {@link #enableBubble}.
21685      *
21686      * @param {String} eventName The name of the event to fire.
21687      * @param {Object...} args Variable number of parameters are passed to handlers.
21688      * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
21689      */
21690     fireEvent: function(eventName) {
21691         var name = eventName.toLowerCase(),
21692             events = this.events,
21693             event = events && events[name],
21694             bubbles = event && event.bubble;
21695
21696         return this.continueFireEvent(name, Ext.Array.slice(arguments, 1), bubbles);
21697     },
21698
21699     /**
21700      * Continue to fire event.
21701      * @private
21702      *
21703      * @param {String} eventName
21704      * @param {Array} args
21705      * @param {Boolean} bubbles
21706      */
21707     continueFireEvent: function(eventName, args, bubbles) {
21708         var target = this,
21709             queue, event,
21710             ret = true;
21711
21712         do {
21713             if (target.eventsSuspended === true) {
21714                 if ((queue = target.eventQueue)) {
21715                     queue.push([eventName, args, bubbles]);
21716                 }
21717                 return ret;
21718             } else {
21719                 event = target.events[eventName];
21720                 // Continue bubbling if event exists and it is `true` or the handler didn't returns false and it
21721                 // configure to bubble.
21722                 if (event && event != true) {
21723                     if ((ret = event.fire.apply(event, args)) === false) {
21724                         break;
21725                     }
21726                 }
21727             }
21728         } while (bubbles && (target = target.getBubbleParent()));
21729         return ret;
21730     },
21731
21732     /**
21733      * Gets the bubbling parent for an Observable
21734      * @private
21735      * @return {Ext.util.Observable} The bubble parent. null is returned if no bubble target exists
21736      */
21737     getBubbleParent: function(){
21738         var me = this, parent = me.getBubbleTarget && me.getBubbleTarget();
21739         if (parent && parent.isObservable) {
21740             return parent;
21741         }
21742         return null;
21743     },
21744
21745     /**
21746      * Appends an event handler to this object.
21747      *
21748      * @param {String} eventName The name of the event to listen for. May also be an object who's property names are
21749      * event names.
21750      * @param {Function} fn The method the event invokes.  Will be called with arguments given to
21751      * {@link #fireEvent} plus the `options` parameter described below.
21752      * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed. **If
21753      * omitted, defaults to the object which fired the event.**
21754      * @param {Object} [options] An object containing handler configuration.
21755      *
21756      * **Note:** Unlike in ExtJS 3.x, the options object will also be passed as the last argument to every event handler.
21757      *
21758      * This object may contain any of the following properties:
21759      *
21760      * - **scope** : Object
21761      *
21762      *   The scope (`this` reference) in which the handler function is executed. **If omitted, defaults to the object
21763      *   which fired the event.**
21764      *
21765      * - **delay** : Number
21766      *
21767      *   The number of milliseconds to delay the invocation of the handler after the event fires.
21768      *
21769      * - **single** : Boolean
21770      *
21771      *   True to add a handler to handle just the next firing of the event, and then remove itself.
21772      *
21773      * - **buffer** : Number
21774      *
21775      *   Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
21776      *   milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
21777      *   handler is scheduled in its place.
21778      *
21779      * - **target** : Observable
21780      *
21781      *   Only call the handler if the event was fired on the target Observable, _not_ if the event was bubbled up from a
21782      *   child Observable.
21783      *
21784      * - **element** : String
21785      *
21786      *   **This option is only valid for listeners bound to {@link Ext.Component Components}.** The name of a Component
21787      *   property which references an element to add a listener to.
21788      *
21789      *   This option is useful during Component construction to add DOM event listeners to elements of
21790      *   {@link Ext.Component Components} which will exist only after the Component is rendered.
21791      *   For example, to add a click listener to a Panel's body:
21792      *
21793      *       new Ext.panel.Panel({
21794      *           title: 'The title',
21795      *           listeners: {
21796      *               click: this.handlePanelClick,
21797      *               element: 'body'
21798      *           }
21799      *       });
21800      *
21801      * **Combining Options**
21802      *
21803      * Using the options argument, it is possible to combine different types of listeners:
21804      *
21805      * A delayed, one-time listener.
21806      *
21807      *     myPanel.on('hide', this.handleClick, this, {
21808      *         single: true,
21809      *         delay: 100
21810      *     });
21811      *
21812      * **Attaching multiple handlers in 1 call**
21813      *
21814      * The method also allows for a single argument to be passed which is a config object containing properties which
21815      * specify multiple events. For example:
21816      *
21817      *     myGridPanel.on({
21818      *         cellClick: this.onCellClick,
21819      *         mouseover: this.onMouseOver,
21820      *         mouseout: this.onMouseOut,
21821      *         scope: this // Important. Ensure "this" is correct during handler execution
21822      *     });
21823      *
21824      * One can also specify options for each event handler separately:
21825      *
21826      *     myGridPanel.on({
21827      *         cellClick: {fn: this.onCellClick, scope: this, single: true},
21828      *         mouseover: {fn: panel.onMouseOver, scope: panel}
21829      *     });
21830      *
21831      */
21832     addListener: function(ename, fn, scope, options) {
21833         var me = this,
21834             config,
21835             event;
21836
21837         if (typeof ename !== 'string') {
21838             options = ename;
21839             for (ename in options) {
21840                 if (options.hasOwnProperty(ename)) {
21841                     config = options[ename];
21842                     if (!me.eventOptionsRe.test(ename)) {
21843                         me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
21844                     }
21845                 }
21846             }
21847         }
21848         else {
21849             ename = ename.toLowerCase();
21850             me.events[ename] = me.events[ename] || true;
21851             event = me.events[ename] || true;
21852             if (Ext.isBoolean(event)) {
21853                 me.events[ename] = event = new Ext.util.Event(me, ename);
21854             }
21855             event.addListener(fn, scope, Ext.isObject(options) ? options : {});
21856         }
21857     },
21858
21859     /**
21860      * Removes an event handler.
21861      *
21862      * @param {String} eventName The type of event the handler was associated with.
21863      * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
21864      * {@link #addListener} call.**
21865      * @param {Object} scope (optional) The scope originally specified for the handler. It must be the same as the
21866      * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
21867      */
21868     removeListener: function(ename, fn, scope) {
21869         var me = this,
21870             config,
21871             event,
21872             options;
21873
21874         if (typeof ename !== 'string') {
21875             options = ename;
21876             for (ename in options) {
21877                 if (options.hasOwnProperty(ename)) {
21878                     config = options[ename];
21879                     if (!me.eventOptionsRe.test(ename)) {
21880                         me.removeListener(ename, config.fn || config, config.scope || options.scope);
21881                     }
21882                 }
21883             }
21884         } else {
21885             ename = ename.toLowerCase();
21886             event = me.events[ename];
21887             if (event && event.isEvent) {
21888                 event.removeListener(fn, scope);
21889             }
21890         }
21891     },
21892
21893     /**
21894      * Removes all listeners for this object including the managed listeners
21895      */
21896     clearListeners: function() {
21897         var events = this.events,
21898             event,
21899             key;
21900
21901         for (key in events) {
21902             if (events.hasOwnProperty(key)) {
21903                 event = events[key];
21904                 if (event.isEvent) {
21905                     event.clearListeners();
21906                 }
21907             }
21908         }
21909
21910         this.clearManagedListeners();
21911     },
21912
21913     purgeListeners : function() {
21914         if (Ext.global.console) {
21915             Ext.global.console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
21916         }
21917         return this.clearListeners.apply(this, arguments);
21918     },
21919
21920     /**
21921      * Removes all managed listeners for this object.
21922      */
21923     clearManagedListeners : function() {
21924         var managedListeners = this.managedListeners || [],
21925             i = 0,
21926             len = managedListeners.length;
21927
21928         for (; i < len; i++) {
21929             this.removeManagedListenerItem(true, managedListeners[i]);
21930         }
21931
21932         this.managedListeners = [];
21933     },
21934
21935     /**
21936      * Remove a single managed listener item
21937      * @private
21938      * @param {Boolean} isClear True if this is being called during a clear
21939      * @param {Object} managedListener The managed listener item
21940      * See removeManagedListener for other args
21941      */
21942     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
21943         if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
21944             managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
21945             if (!isClear) {
21946                 Ext.Array.remove(this.managedListeners, managedListener);
21947             }
21948         }
21949     },
21950
21951     purgeManagedListeners : function() {
21952         if (Ext.global.console) {
21953             Ext.global.console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
21954         }
21955         return this.clearManagedListeners.apply(this, arguments);
21956     },
21957
21958     /**
21959      * Adds the specified events to the list of events which this Observable may fire.
21960      *
21961      * @param {Object/String} o Either an object with event names as properties with a value of `true` or the first
21962      * event name string if multiple event names are being passed as separate parameters. Usage:
21963      *
21964      *     this.addEvents({
21965      *         storeloaded: true,
21966      *         storecleared: true
21967      *     });
21968      *
21969      * @param {String...} more (optional) Additional event names if multiple event names are being passed as separate
21970      * parameters. Usage:
21971      *
21972      *     this.addEvents('storeloaded', 'storecleared');
21973      *
21974      */
21975     addEvents: function(o) {
21976         var me = this,
21977             args,
21978             len,
21979             i;
21980
21981             me.events = me.events || {};
21982         if (Ext.isString(o)) {
21983             args = arguments;
21984             i = args.length;
21985
21986             while (i--) {
21987                 me.events[args[i]] = me.events[args[i]] || true;
21988             }
21989         } else {
21990             Ext.applyIf(me.events, o);
21991         }
21992     },
21993
21994     /**
21995      * Checks to see if this object has any listeners for a specified event
21996      *
21997      * @param {String} eventName The name of the event to check for
21998      * @return {Boolean} True if the event is being listened for, else false
21999      */
22000     hasListener: function(ename) {
22001         var event = this.events[ename.toLowerCase()];
22002         return event && event.isEvent === true && event.listeners.length > 0;
22003     },
22004
22005     /**
22006      * Suspends the firing of all events. (see {@link #resumeEvents})
22007      *
22008      * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
22009      * after the {@link #resumeEvents} call instead of discarding all suspended events.
22010      */
22011     suspendEvents: function(queueSuspended) {
22012         this.eventsSuspended = true;
22013         if (queueSuspended && !this.eventQueue) {
22014             this.eventQueue = [];
22015         }
22016     },
22017
22018     /**
22019      * Resumes firing events (see {@link #suspendEvents}).
22020      *
22021      * If events were suspended using the `queueSuspended` parameter, then all events fired
22022      * during event suspension will be sent to any listeners now.
22023      */
22024     resumeEvents: function() {
22025         var me = this,
22026             queued = me.eventQueue;
22027
22028         me.eventsSuspended = false;
22029         delete me.eventQueue;
22030
22031         if (queued) {
22032             Ext.each(queued, function(e) {
22033                 me.continueFireEvent.apply(me, e);
22034             });
22035         }
22036     },
22037
22038     /**
22039      * Relays selected events from the specified Observable as if the events were fired by `this`.
22040      *
22041      * @param {Object} origin The Observable whose events this object is to relay.
22042      * @param {String[]} events Array of event names to relay.
22043      * @param {String} prefix
22044      */
22045     relayEvents : function(origin, events, prefix) {
22046         prefix = prefix || '';
22047         var me = this,
22048             len = events.length,
22049             i = 0,
22050             oldName,
22051             newName;
22052
22053         for (; i < len; i++) {
22054             oldName = events[i].substr(prefix.length);
22055             newName = prefix + oldName;
22056             me.events[newName] = me.events[newName] || true;
22057             origin.on(oldName, me.createRelayer(newName));
22058         }
22059     },
22060
22061     /**
22062      * @private
22063      * Creates an event handling function which refires the event from this object as the passed event name.
22064      * @param newName
22065      * @returns {Function}
22066      */
22067     createRelayer: function(newName){
22068         var me = this;
22069         return function(){
22070             return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.call(arguments, 0, -1)));
22071         };
22072     },
22073
22074     /**
22075      * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
22076      * present. There is no implementation in the Observable base class.
22077      *
22078      * This is commonly used by Ext.Components to bubble events to owner Containers.
22079      * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component returns the
22080      * Component's immediate owner. But if a known target is required, this can be overridden to access the
22081      * required target more quickly.
22082      *
22083      * Example:
22084      *
22085      *     Ext.override(Ext.form.field.Base, {
22086      *         //  Add functionality to Field's initComponent to enable the change event to bubble
22087      *         initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
22088      *             this.enableBubble('change');
22089      *         }),
22090      *
22091      *         //  We know that we want Field's events to bubble directly to the FormPanel.
22092      *         getBubbleTarget : function() {
22093      *             if (!this.formPanel) {
22094      *                 this.formPanel = this.findParentByType('form');
22095      *             }
22096      *             return this.formPanel;
22097      *         }
22098      *     });
22099      *
22100      *     var myForm = new Ext.formPanel({
22101      *         title: 'User Details',
22102      *         items: [{
22103      *             ...
22104      *         }],
22105      *         listeners: {
22106      *             change: function() {
22107      *                 // Title goes red if form has been modified.
22108      *                 myForm.header.setStyle('color', 'red');
22109      *             }
22110      *         }
22111      *     });
22112      *
22113      * @param {String/String[]} events The event name to bubble, or an Array of event names.
22114      */
22115     enableBubble: function(events) {
22116         var me = this;
22117         if (!Ext.isEmpty(events)) {
22118             events = Ext.isArray(events) ? events: Ext.Array.toArray(arguments);
22119             Ext.each(events,
22120             function(ename) {
22121                 ename = ename.toLowerCase();
22122                 var ce = me.events[ename] || true;
22123                 if (Ext.isBoolean(ce)) {
22124                     ce = new Ext.util.Event(me, ename);
22125                     me.events[ename] = ce;
22126                 }
22127                 ce.bubble = true;
22128             });
22129         }
22130     }
22131 }, function() {
22132
22133     this.createAlias({
22134         /**
22135          * @method
22136          * Shorthand for {@link #addListener}.
22137          * @alias Ext.util.Observable#addListener
22138          */
22139         on: 'addListener',
22140         /**
22141          * @method
22142          * Shorthand for {@link #removeListener}.
22143          * @alias Ext.util.Observable#removeListener
22144          */
22145         un: 'removeListener',
22146         /**
22147          * @method
22148          * Shorthand for {@link #addManagedListener}.
22149          * @alias Ext.util.Observable#addManagedListener
22150          */
22151         mon: 'addManagedListener',
22152         /**
22153          * @method
22154          * Shorthand for {@link #removeManagedListener}.
22155          * @alias Ext.util.Observable#removeManagedListener
22156          */
22157         mun: 'removeManagedListener'
22158     });
22159
22160     //deprecated, will be removed in 5.0
22161     this.observeClass = this.observe;
22162
22163     Ext.apply(Ext.util.Observable.prototype, function(){
22164         // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
22165         // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
22166         // private
22167         function getMethodEvent(method){
22168             var e = (this.methodEvents = this.methodEvents || {})[method],
22169                 returnValue,
22170                 v,
22171                 cancel,
22172                 obj = this;
22173
22174             if (!e) {
22175                 this.methodEvents[method] = e = {};
22176                 e.originalFn = this[method];
22177                 e.methodName = method;
22178                 e.before = [];
22179                 e.after = [];
22180
22181                 var makeCall = function(fn, scope, args){
22182                     if((v = fn.apply(scope || obj, args)) !== undefined){
22183                         if (typeof v == 'object') {
22184                             if(v.returnValue !== undefined){
22185                                 returnValue = v.returnValue;
22186                             }else{
22187                                 returnValue = v;
22188                             }
22189                             cancel = !!v.cancel;
22190                         }
22191                         else
22192                             if (v === false) {
22193                                 cancel = true;
22194                             }
22195                             else {
22196                                 returnValue = v;
22197                             }
22198                     }
22199                 };
22200
22201                 this[method] = function(){
22202                     var args = Array.prototype.slice.call(arguments, 0),
22203                         b, i, len;
22204                     returnValue = v = undefined;
22205                     cancel = false;
22206
22207                     for(i = 0, len = e.before.length; i < len; i++){
22208                         b = e.before[i];
22209                         makeCall(b.fn, b.scope, args);
22210                         if (cancel) {
22211                             return returnValue;
22212                         }
22213                     }
22214
22215                     if((v = e.originalFn.apply(obj, args)) !== undefined){
22216                         returnValue = v;
22217                     }
22218
22219                     for(i = 0, len = e.after.length; i < len; i++){
22220                         b = e.after[i];
22221                         makeCall(b.fn, b.scope, args);
22222                         if (cancel) {
22223                             return returnValue;
22224                         }
22225                     }
22226                     return returnValue;
22227                 };
22228             }
22229             return e;
22230         }
22231
22232         return {
22233             // these are considered experimental
22234             // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
22235             // adds an 'interceptor' called before the original method
22236             beforeMethod : function(method, fn, scope){
22237                 getMethodEvent.call(this, method).before.push({
22238                     fn: fn,
22239                     scope: scope
22240                 });
22241             },
22242
22243             // adds a 'sequence' called after the original method
22244             afterMethod : function(method, fn, scope){
22245                 getMethodEvent.call(this, method).after.push({
22246                     fn: fn,
22247                     scope: scope
22248                 });
22249             },
22250
22251             removeMethodListener: function(method, fn, scope){
22252                 var e = this.getMethodEvent(method),
22253                     i, len;
22254                 for(i = 0, len = e.before.length; i < len; i++){
22255                     if(e.before[i].fn == fn && e.before[i].scope == scope){
22256                         Ext.Array.erase(e.before, i, 1);
22257                         return;
22258                     }
22259                 }
22260                 for(i = 0, len = e.after.length; i < len; i++){
22261                     if(e.after[i].fn == fn && e.after[i].scope == scope){
22262                         Ext.Array.erase(e.after, i, 1);
22263                         return;
22264                     }
22265                 }
22266             },
22267
22268             toggleEventLogging: function(toggle) {
22269                 Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
22270                     if (Ext.isDefined(Ext.global.console)) {
22271                         Ext.global.console.log(en, arguments);
22272                     }
22273                 });
22274             }
22275         };
22276     }());
22277 });
22278
22279 /**
22280  * @class Ext.util.Animate
22281  * This animation class is a mixin.
22282  * 
22283  * Ext.util.Animate provides an API for the creation of animated transitions of properties and styles.  
22284  * This class is used as a mixin and currently applied to {@link Ext.Element}, {@link Ext.CompositeElement}, 
22285  * {@link Ext.draw.Sprite}, {@link Ext.draw.CompositeSprite}, and {@link Ext.Component}.  Note that Components 
22286  * have a limited subset of what attributes can be animated such as top, left, x, y, height, width, and 
22287  * opacity (color, paddings, and margins can not be animated).
22288  * 
22289  * ## Animation Basics
22290  * 
22291  * All animations require three things - `easing`, `duration`, and `to` (the final end value for each property) 
22292  * you wish to animate. Easing and duration are defaulted values specified below.
22293  * Easing describes how the intermediate values used during a transition will be calculated. 
22294  * {@link Ext.fx.Anim#easing Easing} allows for a transition to change speed over its duration.
22295  * You may use the defaults for easing and duration, but you must always set a 
22296  * {@link Ext.fx.Anim#to to} property which is the end value for all animations.  
22297  * 
22298  * Popular element 'to' configurations are:
22299  * 
22300  *  - opacity
22301  *  - x
22302  *  - y
22303  *  - color
22304  *  - height
22305  *  - width 
22306  * 
22307  * Popular sprite 'to' configurations are:
22308  * 
22309  *  - translation
22310  *  - path
22311  *  - scale
22312  *  - stroke
22313  *  - rotation
22314  * 
22315  * The default duration for animations is 250 (which is a 1/4 of a second).  Duration is denoted in 
22316  * milliseconds.  Therefore 1 second is 1000, 1 minute would be 60000, and so on. The default easing curve 
22317  * used for all animations is 'ease'.  Popular easing functions are included and can be found in {@link Ext.fx.Anim#easing Easing}.
22318  * 
22319  * For example, a simple animation to fade out an element with a default easing and duration:
22320  * 
22321  *     var p1 = Ext.get('myElementId');
22322  * 
22323  *     p1.animate({
22324  *         to: {
22325  *             opacity: 0
22326  *         }
22327  *     });
22328  * 
22329  * To make this animation fade out in a tenth of a second:
22330  * 
22331  *     var p1 = Ext.get('myElementId');
22332  * 
22333  *     p1.animate({
22334  *        duration: 100,
22335  *         to: {
22336  *             opacity: 0
22337  *         }
22338  *     });
22339  * 
22340  * ## Animation Queues
22341  * 
22342  * By default all animations are added to a queue which allows for animation via a chain-style API.
22343  * For example, the following code will queue 4 animations which occur sequentially (one right after the other):
22344  * 
22345  *     p1.animate({
22346  *         to: {
22347  *             x: 500
22348  *         }
22349  *     }).animate({
22350  *         to: {
22351  *             y: 150
22352  *         }
22353  *     }).animate({
22354  *         to: {
22355  *             backgroundColor: '#f00'  //red
22356  *         }
22357  *     }).animate({
22358  *         to: {
22359  *             opacity: 0
22360  *         }
22361  *     });
22362  * 
22363  * You can change this behavior by calling the {@link Ext.util.Animate#syncFx syncFx} method and all 
22364  * subsequent animations for the specified target will be run concurrently (at the same time).
22365  * 
22366  *     p1.syncFx();  //this will make all animations run at the same time
22367  * 
22368  *     p1.animate({
22369  *         to: {
22370  *             x: 500
22371  *         }
22372  *     }).animate({
22373  *         to: {
22374  *             y: 150
22375  *         }
22376  *     }).animate({
22377  *         to: {
22378  *             backgroundColor: '#f00'  //red
22379  *         }
22380  *     }).animate({
22381  *         to: {
22382  *             opacity: 0
22383  *         }
22384  *     });
22385  * 
22386  * This works the same as:
22387  * 
22388  *     p1.animate({
22389  *         to: {
22390  *             x: 500,
22391  *             y: 150,
22392  *             backgroundColor: '#f00'  //red
22393  *             opacity: 0
22394  *         }
22395  *     });
22396  * 
22397  * The {@link Ext.util.Animate#stopAnimation stopAnimation} method can be used to stop any 
22398  * currently running animations and clear any queued animations. 
22399  * 
22400  * ## Animation Keyframes
22401  *
22402  * You can also set up complex animations with {@link Ext.fx.Anim#keyframes keyframes} which follow the 
22403  * CSS3 Animation configuration pattern. Note rotation, translation, and scaling can only be done for sprites. 
22404  * The previous example can be written with the following syntax:
22405  * 
22406  *     p1.animate({
22407  *         duration: 1000,  //one second total
22408  *         keyframes: {
22409  *             25: {     //from 0 to 250ms (25%)
22410  *                 x: 0
22411  *             },
22412  *             50: {   //from 250ms to 500ms (50%)
22413  *                 y: 0
22414  *             },
22415  *             75: {  //from 500ms to 750ms (75%)
22416  *                 backgroundColor: '#f00'  //red
22417  *             },
22418  *             100: {  //from 750ms to 1sec
22419  *                 opacity: 0
22420  *             }
22421  *         }
22422  *     });
22423  * 
22424  * ## Animation Events
22425  * 
22426  * Each animation you create has events for {@link Ext.fx.Anim#beforeanimate beforeanimate}, 
22427  * {@link Ext.fx.Anim#afteranimate afteranimate}, and {@link Ext.fx.Anim#lastframe lastframe}.  
22428  * Keyframed animations adds an additional {@link Ext.fx.Animator#keyframe keyframe} event which 
22429  * fires for each keyframe in your animation.
22430  * 
22431  * All animations support the {@link Ext.util.Observable#listeners listeners} configuration to attact functions to these events.
22432  *    
22433  *     startAnimate: function() {
22434  *         var p1 = Ext.get('myElementId');
22435  *         p1.animate({
22436  *            duration: 100,
22437  *             to: {
22438  *                 opacity: 0
22439  *             },
22440  *             listeners: {
22441  *                 beforeanimate:  function() {
22442  *                     // Execute my custom method before the animation
22443  *                     this.myBeforeAnimateFn();
22444  *                 },
22445  *                 afteranimate: function() {
22446  *                     // Execute my custom method after the animation
22447  *                     this.myAfterAnimateFn();
22448  *                 },
22449  *                 scope: this
22450  *         });
22451  *     },
22452  *     myBeforeAnimateFn: function() {
22453  *       // My custom logic
22454  *     },
22455  *     myAfterAnimateFn: function() {
22456  *       // My custom logic
22457  *     }
22458  * 
22459  * Due to the fact that animations run asynchronously, you can determine if an animation is currently 
22460  * running on any target by using the {@link Ext.util.Animate#getActiveAnimation getActiveAnimation} 
22461  * method.  This method will return false if there are no active animations or return the currently 
22462  * running {@link Ext.fx.Anim} instance.
22463  * 
22464  * In this example, we're going to wait for the current animation to finish, then stop any other 
22465  * queued animations before we fade our element's opacity to 0:
22466  * 
22467  *     var curAnim = p1.getActiveAnimation();
22468  *     if (curAnim) {
22469  *         curAnim.on('afteranimate', function() {
22470  *             p1.stopAnimation();
22471  *             p1.animate({
22472  *                 to: {
22473  *                     opacity: 0
22474  *                 }
22475  *             });
22476  *         });
22477  *     }
22478  * 
22479  * @docauthor Jamie Avins <jamie@sencha.com>
22480  */
22481 Ext.define('Ext.util.Animate', {
22482
22483     uses: ['Ext.fx.Manager', 'Ext.fx.Anim'],
22484
22485     /**
22486      * <p>Perform custom animation on this object.<p>
22487      * <p>This method is applicable to both the {@link Ext.Component Component} class and the {@link Ext.Element Element} class.
22488      * It performs animated transitions of certain properties of this object over a specified timeline.</p>
22489      * <p>The sole parameter is an object which specifies start property values, end property values, and properties which
22490      * describe the timeline. Of the properties listed below, only <b><code>to</code></b> is mandatory.</p>
22491      * <p>Properties include<ul>
22492      * <li><code>from</code> <div class="sub-desc">An object which specifies start values for the properties being animated.
22493      * If not supplied, properties are animated from current settings. The actual properties which may be animated depend upon
22494      * ths object being animated. See the sections below on Element and Component animation.<div></li>
22495      * <li><code>to</code> <div class="sub-desc">An object which specifies end values for the properties being animated.</div></li>
22496      * <li><code>duration</code><div class="sub-desc">The duration <b>in milliseconds</b> for which the animation will run.</div></li>
22497      * <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>
22498      * <li>ease</li>
22499      * <li>easeIn</li>
22500      * <li>easeOut</li>
22501      * <li>easeInOut</li>
22502      * <li>backIn</li>
22503      * <li>backOut</li>
22504      * <li>elasticIn</li>
22505      * <li>elasticOut</li>
22506      * <li>bounceIn</li>
22507      * <li>bounceOut</li>
22508      * </ul></code></div></li>
22509      * <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.
22510      * 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>
22511      * <li><code>listeners</code> <div class="sub-desc">This is a standard {@link Ext.util.Observable#listeners listeners} configuration object which may be used
22512      * to inject behaviour at either the <code>beforeanimate</code> event or the <code>afteranimate</code> event.</div></li>
22513      * </ul></p>
22514      * <h3>Animating an {@link Ext.Element Element}</h3>
22515      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
22516      * <li><code>x</code> <div class="sub-desc">The page X position in pixels.</div></li>
22517      * <li><code>y</code> <div class="sub-desc">The page Y position in pixels</div></li>
22518      * <li><code>left</code> <div class="sub-desc">The element's CSS <code>left</code> value. Units must be supplied.</div></li>
22519      * <li><code>top</code> <div class="sub-desc">The element's CSS <code>top</code> value. Units must be supplied.</div></li>
22520      * <li><code>width</code> <div class="sub-desc">The element's CSS <code>width</code> value. Units must be supplied.</div></li>
22521      * <li><code>height</code> <div class="sub-desc">The element's CSS <code>height</code> value. Units must be supplied.</div></li>
22522      * <li><code>scrollLeft</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
22523      * <li><code>scrollTop</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
22524      * <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>
22525      * </ul>
22526      * <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
22527      * 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
22528      * directly animate certain properties of Components.</b></p>
22529      * <h3>Animating a {@link Ext.Component Component}</h3>
22530      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
22531      * <li><code>x</code> <div class="sub-desc">The Component's page X position in pixels.</div></li>
22532      * <li><code>y</code> <div class="sub-desc">The Component's page Y position in pixels</div></li>
22533      * <li><code>left</code> <div class="sub-desc">The Component's <code>left</code> value in pixels.</div></li>
22534      * <li><code>top</code> <div class="sub-desc">The Component's <code>top</code> value in pixels.</div></li>
22535      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
22536      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
22537      * <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
22538      * of the animation. <i>Use sparingly as laying out on every intermediate size change is an expensive operation</i>.</div></li>
22539      * </ul>
22540      * <p>For example, to animate a Window to a new size, ensuring that its internal layout, and any shadow is correct:</p>
22541      * <pre><code>
22542 myWindow = Ext.create('Ext.window.Window', {
22543     title: 'Test Component animation',
22544     width: 500,
22545     height: 300,
22546     layout: {
22547         type: 'hbox',
22548         align: 'stretch'
22549     },
22550     items: [{
22551         title: 'Left: 33%',
22552         margins: '5 0 5 5',
22553         flex: 1
22554     }, {
22555         title: 'Left: 66%',
22556         margins: '5 5 5 5',
22557         flex: 2
22558     }]
22559 });
22560 myWindow.show();
22561 myWindow.header.el.on('click', function() {
22562     myWindow.animate({
22563         to: {
22564             width: (myWindow.getWidth() == 500) ? 700 : 500,
22565             height: (myWindow.getHeight() == 300) ? 400 : 300,
22566         }
22567     });
22568 });
22569 </code></pre>
22570      * <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
22571      * 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>
22572      * @param {Object} config An object containing properties which describe the animation's start and end states, and the timeline of the animation.
22573      * @return {Object} this
22574      */
22575     animate: function(animObj) {
22576         var me = this;
22577         if (Ext.fx.Manager.hasFxBlock(me.id)) {
22578             return me;
22579         }
22580         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(animObj)));
22581         return this;
22582     },
22583
22584     // @private - process the passed fx configuration.
22585     anim: function(config) {
22586         if (!Ext.isObject(config)) {
22587             return (config) ? {} : false;
22588         }
22589
22590         var me = this;
22591
22592         if (config.stopAnimation) {
22593             me.stopAnimation();
22594         }
22595
22596         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
22597
22598         return Ext.apply({
22599             target: me,
22600             paused: true
22601         }, config);
22602     },
22603
22604     /**
22605      * @deprecated 4.0 Replaced by {@link #stopAnimation}
22606      * Stops any running effects and clears this object's internal effects queue if it contains
22607      * any additional effects that haven't started yet.
22608      * @return {Ext.Element} The Element
22609      * @method
22610      */
22611     stopFx: Ext.Function.alias(Ext.util.Animate, 'stopAnimation'),
22612
22613     /**
22614      * Stops any running effects and clears this object's internal effects queue if it contains
22615      * any additional effects that haven't started yet.
22616      * @return {Ext.Element} The Element
22617      */
22618     stopAnimation: function() {
22619         Ext.fx.Manager.stopAnimation(this.id);
22620         return this;
22621     },
22622
22623     /**
22624      * Ensures that all effects queued after syncFx is called on this object are
22625      * run concurrently.  This is the opposite of {@link #sequenceFx}.
22626      * @return {Object} this
22627      */
22628     syncFx: function() {
22629         Ext.fx.Manager.setFxDefaults(this.id, {
22630             concurrent: true
22631         });
22632         return this;
22633     },
22634
22635     /**
22636      * Ensures that all effects queued after sequenceFx is called on this object are
22637      * run in sequence.  This is the opposite of {@link #syncFx}.
22638      * @return {Object} this
22639      */
22640     sequenceFx: function() {
22641         Ext.fx.Manager.setFxDefaults(this.id, {
22642             concurrent: false
22643         });
22644         return this;
22645     },
22646
22647     /**
22648      * @deprecated 4.0 Replaced by {@link #getActiveAnimation}
22649      * @alias Ext.util.Animate#getActiveAnimation
22650      * @method
22651      */
22652     hasActiveFx: Ext.Function.alias(Ext.util.Animate, 'getActiveAnimation'),
22653
22654     /**
22655      * Returns the current animation if this object has any effects actively running or queued, else returns false.
22656      * @return {Ext.fx.Anim/Boolean} Anim if element has active effects, else false
22657      */
22658     getActiveAnimation: function() {
22659         return Ext.fx.Manager.getActiveAnimation(this.id);
22660     }
22661 }, function(){
22662     // Apply Animate mixin manually until Element is defined in the proper 4.x way
22663     Ext.applyIf(Ext.Element.prototype, this.prototype);
22664     // We need to call this again so the animation methods get copied over to CE
22665     Ext.CompositeElementLite.importElementMethods();
22666 });
22667 /**
22668  * @class Ext.state.Provider
22669  * <p>Abstract base class for state provider implementations. The provider is responsible
22670  * for setting values  and extracting values to/from the underlying storage source. The 
22671  * storage source can vary and the details should be implemented in a subclass. For example
22672  * a provider could use a server side database or the browser localstorage where supported.</p>
22673  *
22674  * <p>This class provides methods for encoding and decoding <b>typed</b> variables including 
22675  * dates and defines the Provider interface. By default these methods put the value and the
22676  * type information into a delimited string that can be stored. These should be overridden in 
22677  * a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
22678  */
22679 Ext.define('Ext.state.Provider', {
22680     mixins: {
22681         observable: 'Ext.util.Observable'
22682     },
22683     
22684     /**
22685      * @cfg {String} prefix A string to prefix to items stored in the underlying state store. 
22686      * Defaults to <tt>'ext-'</tt>
22687      */
22688     prefix: 'ext-',
22689     
22690     constructor : function(config){
22691         config = config || {};
22692         var me = this;
22693         Ext.apply(me, config);
22694         /**
22695          * @event statechange
22696          * Fires when a state change occurs.
22697          * @param {Ext.state.Provider} this This state provider
22698          * @param {String} key The state key which was changed
22699          * @param {String} value The encoded value for the state
22700          */
22701         me.addEvents("statechange");
22702         me.state = {};
22703         me.mixins.observable.constructor.call(me);
22704     },
22705     
22706     /**
22707      * Returns the current value for a key
22708      * @param {String} name The key name
22709      * @param {Object} defaultValue A default value to return if the key's value is not found
22710      * @return {Object} The state data
22711      */
22712     get : function(name, defaultValue){
22713         return typeof this.state[name] == "undefined" ?
22714             defaultValue : this.state[name];
22715     },
22716
22717     /**
22718      * Clears a value from the state
22719      * @param {String} name The key name
22720      */
22721     clear : function(name){
22722         var me = this;
22723         delete me.state[name];
22724         me.fireEvent("statechange", me, name, null);
22725     },
22726
22727     /**
22728      * Sets the value for a key
22729      * @param {String} name The key name
22730      * @param {Object} value The value to set
22731      */
22732     set : function(name, value){
22733         var me = this;
22734         me.state[name] = value;
22735         me.fireEvent("statechange", me, name, value);
22736     },
22737
22738     /**
22739      * Decodes a string previously encoded with {@link #encodeValue}.
22740      * @param {String} value The value to decode
22741      * @return {Object} The decoded value
22742      */
22743     decodeValue : function(value){
22744
22745         // a -> Array
22746         // n -> Number
22747         // d -> Date
22748         // b -> Boolean
22749         // s -> String
22750         // o -> Object
22751         // -> Empty (null)
22752
22753         var me = this,
22754             re = /^(a|n|d|b|s|o|e)\:(.*)$/,
22755             matches = re.exec(unescape(value)),
22756             all,
22757             type,
22758             value,
22759             keyValue;
22760             
22761         if(!matches || !matches[1]){
22762             return; // non state
22763         }
22764         
22765         type = matches[1];
22766         value = matches[2];
22767         switch (type) {
22768             case 'e':
22769                 return null;
22770             case 'n':
22771                 return parseFloat(value);
22772             case 'd':
22773                 return new Date(Date.parse(value));
22774             case 'b':
22775                 return (value == '1');
22776             case 'a':
22777                 all = [];
22778                 if(value != ''){
22779                     Ext.each(value.split('^'), function(val){
22780                         all.push(me.decodeValue(val));
22781                     }, me);
22782                 }
22783                 return all;
22784            case 'o':
22785                 all = {};
22786                 if(value != ''){
22787                     Ext.each(value.split('^'), function(val){
22788                         keyValue = val.split('=');
22789                         all[keyValue[0]] = me.decodeValue(keyValue[1]);
22790                     }, me);
22791                 }
22792                 return all;
22793            default:
22794                 return value;
22795         }
22796     },
22797
22798     /**
22799      * Encodes a value including type information.  Decode with {@link #decodeValue}.
22800      * @param {Object} value The value to encode
22801      * @return {String} The encoded value
22802      */
22803     encodeValue : function(value){
22804         var flat = '',
22805             i = 0,
22806             enc,
22807             len,
22808             key;
22809             
22810         if (value == null) {
22811             return 'e:1';    
22812         } else if(typeof value == 'number') {
22813             enc = 'n:' + value;
22814         } else if(typeof value == 'boolean') {
22815             enc = 'b:' + (value ? '1' : '0');
22816         } else if(Ext.isDate(value)) {
22817             enc = 'd:' + value.toGMTString();
22818         } else if(Ext.isArray(value)) {
22819             for (len = value.length; i < len; i++) {
22820                 flat += this.encodeValue(value[i]);
22821                 if (i != len - 1) {
22822                     flat += '^';
22823                 }
22824             }
22825             enc = 'a:' + flat;
22826         } else if (typeof value == 'object') {
22827             for (key in value) {
22828                 if (typeof value[key] != 'function' && value[key] !== undefined) {
22829                     flat += key + '=' + this.encodeValue(value[key]) + '^';
22830                 }
22831             }
22832             enc = 'o:' + flat.substring(0, flat.length-1);
22833         } else {
22834             enc = 's:' + value;
22835         }
22836         return escape(enc);
22837     }
22838 });
22839 /**
22840  * Provides searching of Components within Ext.ComponentManager (globally) or a specific
22841  * Ext.container.Container on the document with a similar syntax to a CSS selector.
22842  *
22843  * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
22844  *
22845  * - `component` or `.component`
22846  * - `gridpanel` or `.gridpanel`
22847  *
22848  * An itemId or id must be prefixed with a #
22849  *
22850  * - `#myContainer`
22851  *
22852  * Attributes must be wrapped in brackets
22853  *
22854  * - `component[autoScroll]`
22855  * - `panel[title="Test"]`
22856  *
22857  * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
22858  * the candidate Component will be included in the query:
22859  *
22860  *     var disabledFields = myFormPanel.query("{isDisabled()}");
22861  *
22862  * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
22863  *
22864  *     // Function receives array and returns a filtered array.
22865  *     Ext.ComponentQuery.pseudos.invalid = function(items) {
22866  *         var i = 0, l = items.length, c, result = [];
22867  *         for (; i < l; i++) {
22868  *             if (!(c = items[i]).isValid()) {
22869  *                 result.push(c);
22870  *             }
22871  *         }
22872  *         return result;
22873  *     };
22874  *      
22875  *     var invalidFields = myFormPanel.query('field:invalid');
22876  *     if (invalidFields.length) {
22877  *         invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
22878  *         for (var i = 0, l = invalidFields.length; i < l; i++) {
22879  *             invalidFields[i].getEl().frame("red");
22880  *         }
22881  *     }
22882  *
22883  * Default pseudos include:
22884  *
22885  * - not
22886  * - last
22887  *
22888  * Queries return an array of components.
22889  * Here are some example queries.
22890  *
22891  *     // retrieve all Ext.Panels in the document by xtype
22892  *     var panelsArray = Ext.ComponentQuery.query('panel');
22893  *
22894  *     // retrieve all Ext.Panels within the container with an id myCt
22895  *     var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
22896  *
22897  *     // retrieve all direct children which are Ext.Panels within myCt
22898  *     var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
22899  *
22900  *     // retrieve all grids and trees
22901  *     var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
22902  *
22903  * For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
22904  * {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
22905  * {@link Ext.Component#up}.
22906  */
22907 Ext.define('Ext.ComponentQuery', {
22908     singleton: true,
22909     uses: ['Ext.ComponentManager']
22910 }, function() {
22911
22912     var cq = this,
22913
22914         // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
22915         // as a member on each item in the passed array.
22916         filterFnPattern = [
22917             'var r = [],',
22918                 'i = 0,',
22919                 'it = items,',
22920                 'l = it.length,',
22921                 'c;',
22922             'for (; i < l; i++) {',
22923                 'c = it[i];',
22924                 'if (c.{0}) {',
22925                    'r.push(c);',
22926                 '}',
22927             '}',
22928             'return r;'
22929         ].join(''),
22930
22931         filterItems = function(items, operation) {
22932             // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
22933             // The operation's method loops over each item in the candidate array and
22934             // returns an array of items which match its criteria
22935             return operation.method.apply(this, [ items ].concat(operation.args));
22936         },
22937
22938         getItems = function(items, mode) {
22939             var result = [],
22940                 i = 0,
22941                 length = items.length,
22942                 candidate,
22943                 deep = mode !== '>';
22944                 
22945             for (; i < length; i++) {
22946                 candidate = items[i];
22947                 if (candidate.getRefItems) {
22948                     result = result.concat(candidate.getRefItems(deep));
22949                 }
22950             }
22951             return result;
22952         },
22953
22954         getAncestors = function(items) {
22955             var result = [],
22956                 i = 0,
22957                 length = items.length,
22958                 candidate;
22959             for (; i < length; i++) {
22960                 candidate = items[i];
22961                 while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
22962                     result.push(candidate);
22963                 }
22964             }
22965             return result;
22966         },
22967
22968         // Filters the passed candidate array and returns only items which match the passed xtype
22969         filterByXType = function(items, xtype, shallow) {
22970             if (xtype === '*') {
22971                 return items.slice();
22972             }
22973             else {
22974                 var result = [],
22975                     i = 0,
22976                     length = items.length,
22977                     candidate;
22978                 for (; i < length; i++) {
22979                     candidate = items[i];
22980                     if (candidate.isXType(xtype, shallow)) {
22981                         result.push(candidate);
22982                     }
22983                 }
22984                 return result;
22985             }
22986         },
22987
22988         // Filters the passed candidate array and returns only items which have the passed className
22989         filterByClassName = function(items, className) {
22990             var EA = Ext.Array,
22991                 result = [],
22992                 i = 0,
22993                 length = items.length,
22994                 candidate;
22995             for (; i < length; i++) {
22996                 candidate = items[i];
22997                 if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
22998                     result.push(candidate);
22999                 }
23000             }
23001             return result;
23002         },
23003
23004         // Filters the passed candidate array and returns only items which have the specified property match
23005         filterByAttribute = function(items, property, operator, value) {
23006             var result = [],
23007                 i = 0,
23008                 length = items.length,
23009                 candidate;
23010             for (; i < length; i++) {
23011                 candidate = items[i];
23012                 if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
23013                     result.push(candidate);
23014                 }
23015             }
23016             return result;
23017         },
23018
23019         // Filters the passed candidate array and returns only items which have the specified itemId or id
23020         filterById = function(items, id) {
23021             var result = [],
23022                 i = 0,
23023                 length = items.length,
23024                 candidate;
23025             for (; i < length; i++) {
23026                 candidate = items[i];
23027                 if (candidate.getItemId() === id) {
23028                     result.push(candidate);
23029                 }
23030             }
23031             return result;
23032         },
23033
23034         // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
23035         filterByPseudo = function(items, name, value) {
23036             return cq.pseudos[name](items, value);
23037         },
23038
23039         // Determines leading mode
23040         // > for direct child, and ^ to switch to ownerCt axis
23041         modeRe = /^(\s?([>\^])\s?|\s|$)/,
23042
23043         // Matches a token with possibly (true|false) appended for the "shallow" parameter
23044         tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
23045
23046         matchers = [{
23047             // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
23048             re: /^\.([\w\-]+)(?:\((true|false)\))?/,
23049             method: filterByXType
23050         },{
23051             // checks for [attribute=value]
23052             re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
23053             method: filterByAttribute
23054         }, {
23055             // checks for #cmpItemId
23056             re: /^#([\w\-]+)/,
23057             method: filterById
23058         }, {
23059             // checks for :<pseudo_class>(<selector>)
23060             re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
23061             method: filterByPseudo
23062         }, {
23063             // checks for {<member_expression>}
23064             re: /^(?:\{([^\}]+)\})/,
23065             method: filterFnPattern
23066         }];
23067
23068     // @class Ext.ComponentQuery.Query
23069     // This internal class is completely hidden in documentation.
23070     cq.Query = Ext.extend(Object, {
23071         constructor: function(cfg) {
23072             cfg = cfg || {};
23073             Ext.apply(this, cfg);
23074         },
23075
23076         // Executes this Query upon the selected root.
23077         // The root provides the initial source of candidate Component matches which are progressively
23078         // filtered by iterating through this Query's operations cache.
23079         // If no root is provided, all registered Components are searched via the ComponentManager.
23080         // root may be a Container who's descendant Components are filtered
23081         // root may be a Component with an implementation of getRefItems which provides some nested Components such as the
23082         // docked items within a Panel.
23083         // root may be an array of candidate Components to filter using this Query.
23084         execute : function(root) {
23085             var operations = this.operations,
23086                 i = 0,
23087                 length = operations.length,
23088                 operation,
23089                 workingItems;
23090
23091             // no root, use all Components in the document
23092             if (!root) {
23093                 workingItems = Ext.ComponentManager.all.getArray();
23094             }
23095             // Root is a candidate Array
23096             else if (Ext.isArray(root)) {
23097                 workingItems = root;
23098             }
23099
23100             // We are going to loop over our operations and take care of them
23101             // one by one.
23102             for (; i < length; i++) {
23103                 operation = operations[i];
23104
23105                 // The mode operation requires some custom handling.
23106                 // All other operations essentially filter down our current
23107                 // working items, while mode replaces our current working
23108                 // items by getting children from each one of our current
23109                 // working items. The type of mode determines the type of
23110                 // children we get. (e.g. > only gets direct children)
23111                 if (operation.mode === '^') {
23112                     workingItems = getAncestors(workingItems || [root]);
23113                 }
23114                 else if (operation.mode) {
23115                     workingItems = getItems(workingItems || [root], operation.mode);
23116                 }
23117                 else {
23118                     workingItems = filterItems(workingItems || getItems([root]), operation);
23119                 }
23120
23121                 // If this is the last operation, it means our current working
23122                 // items are the final matched items. Thus return them!
23123                 if (i === length -1) {
23124                     return workingItems;
23125                 }
23126             }
23127             return [];
23128         },
23129
23130         is: function(component) {
23131             var operations = this.operations,
23132                 components = Ext.isArray(component) ? component : [component],
23133                 originalLength = components.length,
23134                 lastOperation = operations[operations.length-1],
23135                 ln, i;
23136
23137             components = filterItems(components, lastOperation);
23138             if (components.length === originalLength) {
23139                 if (operations.length > 1) {
23140                     for (i = 0, ln = components.length; i < ln; i++) {
23141                         if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
23142                             return false;
23143                         }
23144                     }
23145                 }
23146                 return true;
23147             }
23148             return false;
23149         }
23150     });
23151
23152     Ext.apply(this, {
23153
23154         // private cache of selectors and matching ComponentQuery.Query objects
23155         cache: {},
23156
23157         // private cache of pseudo class filter functions
23158         pseudos: {
23159             not: function(components, selector){
23160                 var CQ = Ext.ComponentQuery,
23161                     i = 0,
23162                     length = components.length,
23163                     results = [],
23164                     index = -1,
23165                     component;
23166                 
23167                 for(; i < length; ++i) {
23168                     component = components[i];
23169                     if (!CQ.is(component, selector)) {
23170                         results[++index] = component;
23171                     }
23172                 }
23173                 return results;
23174             },
23175             last: function(components) {
23176                 return components[components.length - 1];
23177             }
23178         },
23179
23180         /**
23181          * Returns an array of matched Components from within the passed root object.
23182          *
23183          * This method filters returned Components in a similar way to how CSS selector based DOM
23184          * queries work using a textual selector string.
23185          *
23186          * See class summary for details.
23187          *
23188          * @param {String} selector The selector string to filter returned Components
23189          * @param {Ext.container.Container} root The Container within which to perform the query.
23190          * If omitted, all Components within the document are included in the search.
23191          * 
23192          * This parameter may also be an array of Components to filter according to the selector.</p>
23193          * @returns {Ext.Component[]} The matched Components.
23194          * 
23195          * @member Ext.ComponentQuery
23196          */
23197         query: function(selector, root) {
23198             var selectors = selector.split(','),
23199                 length = selectors.length,
23200                 i = 0,
23201                 results = [],
23202                 noDupResults = [], 
23203                 dupMatcher = {}, 
23204                 query, resultsLn, cmp;
23205
23206             for (; i < length; i++) {
23207                 selector = Ext.String.trim(selectors[i]);
23208                 query = this.cache[selector];
23209                 if (!query) {
23210                     this.cache[selector] = query = this.parse(selector);
23211                 }
23212                 results = results.concat(query.execute(root));
23213             }
23214
23215             // multiple selectors, potential to find duplicates
23216             // lets filter them out.
23217             if (length > 1) {
23218                 resultsLn = results.length;
23219                 for (i = 0; i < resultsLn; i++) {
23220                     cmp = results[i];
23221                     if (!dupMatcher[cmp.id]) {
23222                         noDupResults.push(cmp);
23223                         dupMatcher[cmp.id] = true;
23224                     }
23225                 }
23226                 results = noDupResults;
23227             }
23228             return results;
23229         },
23230
23231         /**
23232          * Tests whether the passed Component matches the selector string.
23233          * @param {Ext.Component} component The Component to test
23234          * @param {String} selector The selector string to test against.
23235          * @return {Boolean} True if the Component matches the selector.
23236          * @member Ext.ComponentQuery
23237          */
23238         is: function(component, selector) {
23239             if (!selector) {
23240                 return true;
23241             }
23242             var query = this.cache[selector];
23243             if (!query) {
23244                 this.cache[selector] = query = this.parse(selector);
23245             }
23246             return query.is(component);
23247         },
23248
23249         parse: function(selector) {
23250             var operations = [],
23251                 length = matchers.length,
23252                 lastSelector,
23253                 tokenMatch,
23254                 matchedChar,
23255                 modeMatch,
23256                 selectorMatch,
23257                 i, matcher, method;
23258
23259             // We are going to parse the beginning of the selector over and
23260             // over again, slicing off the selector any portions we converted into an
23261             // operation, until it is an empty string.
23262             while (selector && lastSelector !== selector) {
23263                 lastSelector = selector;
23264
23265                 // First we check if we are dealing with a token like #, * or an xtype
23266                 tokenMatch = selector.match(tokenRe);
23267
23268                 if (tokenMatch) {
23269                     matchedChar = tokenMatch[1];
23270
23271                     // If the token is prefixed with a # we push a filterById operation to our stack
23272                     if (matchedChar === '#') {
23273                         operations.push({
23274                             method: filterById,
23275                             args: [Ext.String.trim(tokenMatch[2])]
23276                         });
23277                     }
23278                     // If the token is prefixed with a . we push a filterByClassName operation to our stack
23279                     // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
23280                     else if (matchedChar === '.') {
23281                         operations.push({
23282                             method: filterByClassName,
23283                             args: [Ext.String.trim(tokenMatch[2])]
23284                         });
23285                     }
23286                     // If the token is a * or an xtype string, we push a filterByXType
23287                     // operation to the stack.
23288                     else {
23289                         operations.push({
23290                             method: filterByXType,
23291                             args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
23292                         });
23293                     }
23294
23295                     // Now we slice of the part we just converted into an operation
23296                     selector = selector.replace(tokenMatch[0], '');
23297                 }
23298
23299                 // If the next part of the query is not a space or > or ^, it means we
23300                 // are going to check for more things that our current selection
23301                 // has to comply to.
23302                 while (!(modeMatch = selector.match(modeRe))) {
23303                     // Lets loop over each type of matcher and execute it
23304                     // on our current selector.
23305                     for (i = 0; selector && i < length; i++) {
23306                         matcher = matchers[i];
23307                         selectorMatch = selector.match(matcher.re);
23308                         method = matcher.method;
23309
23310                         // If we have a match, add an operation with the method
23311                         // associated with this matcher, and pass the regular
23312                         // expression matches are arguments to the operation.
23313                         if (selectorMatch) {
23314                             operations.push({
23315                                 method: Ext.isString(matcher.method)
23316                                     // Turn a string method into a function by formatting the string with our selector matche expression
23317                                     // A new method is created for different match expressions, eg {id=='textfield-1024'}
23318                                     // Every expression may be different in different selectors.
23319                                     ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
23320                                     : matcher.method,
23321                                 args: selectorMatch.slice(1)
23322                             });
23323                             selector = selector.replace(selectorMatch[0], '');
23324                             break; // Break on match
23325                         }
23326                         // Exhausted all matches: It's an error
23327                         if (i === (length - 1)) {
23328                             Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
23329                         }
23330                     }
23331                 }
23332
23333                 // Now we are going to check for a mode change. This means a space
23334                 // or a > to determine if we are going to select all the children
23335                 // of the currently matched items, or a ^ if we are going to use the
23336                 // ownerCt axis as the candidate source.
23337                 if (modeMatch[1]) { // Assignment, and test for truthiness!
23338                     operations.push({
23339                         mode: modeMatch[2]||modeMatch[1]
23340                     });
23341                     selector = selector.replace(modeMatch[0], '');
23342                 }
23343             }
23344
23345             //  Now that we have all our operations in an array, we are going
23346             // to create a new Query using these operations.
23347             return new cq.Query({
23348                 operations: operations
23349             });
23350         }
23351     });
23352 });
23353 /**
23354  * @class Ext.util.HashMap
23355  * <p>
23356  * Represents a collection of a set of key and value pairs. Each key in the HashMap
23357  * must be unique, the same key cannot exist twice. Access to items is provided via
23358  * the key only. Sample usage:
23359  * <pre><code>
23360 var map = new Ext.util.HashMap();
23361 map.add('key1', 1);
23362 map.add('key2', 2);
23363 map.add('key3', 3);
23364
23365 map.each(function(key, value, length){
23366     console.log(key, value, length);
23367 });
23368  * </code></pre>
23369  * </p>
23370  *
23371  * <p>The HashMap is an unordered class,
23372  * there is no guarantee when iterating over the items that they will be in any particular
23373  * order. If this is required, then use a {@link Ext.util.MixedCollection}.
23374  * </p>
23375  */
23376 Ext.define('Ext.util.HashMap', {
23377     mixins: {
23378         observable: 'Ext.util.Observable'
23379     },
23380
23381     /**
23382      * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
23383      * A default is provided that returns the <b>id</b> property on the object. This function is only used
23384      * if the add method is called with a single argument.
23385      */
23386
23387     /**
23388      * Creates new HashMap.
23389      * @param {Object} config (optional) Config object.
23390      */
23391     constructor: function(config) {
23392         config = config || {};
23393         
23394         var me = this,
23395             keyFn = config.keyFn;
23396
23397         me.addEvents(
23398             /**
23399              * @event add
23400              * Fires when a new item is added to the hash
23401              * @param {Ext.util.HashMap} this.
23402              * @param {String} key The key of the added item.
23403              * @param {Object} value The value of the added item.
23404              */
23405             'add',
23406             /**
23407              * @event clear
23408              * Fires when the hash is cleared.
23409              * @param {Ext.util.HashMap} this.
23410              */
23411             'clear',
23412             /**
23413              * @event remove
23414              * Fires when an item is removed from the hash.
23415              * @param {Ext.util.HashMap} this.
23416              * @param {String} key The key of the removed item.
23417              * @param {Object} value The value of the removed item.
23418              */
23419             'remove',
23420             /**
23421              * @event replace
23422              * Fires when an item is replaced in the hash.
23423              * @param {Ext.util.HashMap} this.
23424              * @param {String} key The key of the replaced item.
23425              * @param {Object} value The new value for the item.
23426              * @param {Object} old The old value for the item.
23427              */
23428             'replace'
23429         );
23430
23431         me.mixins.observable.constructor.call(me, config);
23432         me.clear(true);
23433         
23434         if (keyFn) {
23435             me.getKey = keyFn;
23436         }
23437     },
23438
23439     /**
23440      * Gets the number of items in the hash.
23441      * @return {Number} The number of items in the hash.
23442      */
23443     getCount: function() {
23444         return this.length;
23445     },
23446
23447     /**
23448      * Implementation for being able to extract the key from an object if only
23449      * a single argument is passed.
23450      * @private
23451      * @param {String} key The key
23452      * @param {Object} value The value
23453      * @return {Array} [key, value]
23454      */
23455     getData: function(key, value) {
23456         // if we have no value, it means we need to get the key from the object
23457         if (value === undefined) {
23458             value = key;
23459             key = this.getKey(value);
23460         }
23461
23462         return [key, value];
23463     },
23464
23465     /**
23466      * Extracts the key from an object. This is a default implementation, it may be overridden
23467      * @param {Object} o The object to get the key from
23468      * @return {String} The key to use.
23469      */
23470     getKey: function(o) {
23471         return o.id;
23472     },
23473
23474     /**
23475      * Adds an item to the collection. Fires the {@link #add} event when complete.
23476      * @param {String} key <p>The key to associate with the item, or the new item.</p>
23477      * <p>If a {@link #getKey} implementation was specified for this HashMap,
23478      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
23479      * the HashMap will be able to <i>derive</i> the key for the new item.
23480      * In this case just pass the new item in this parameter.</p>
23481      * @param {Object} o The item to add.
23482      * @return {Object} The item added.
23483      */
23484     add: function(key, value) {
23485         var me = this,
23486             data;
23487
23488         if (arguments.length === 1) {
23489             value = key;
23490             key = me.getKey(value);
23491         }
23492
23493         if (me.containsKey(key)) {
23494             return me.replace(key, value);
23495         }
23496
23497         data = me.getData(key, value);
23498         key = data[0];
23499         value = data[1];
23500         me.map[key] = value;
23501         ++me.length;
23502         me.fireEvent('add', me, key, value);
23503         return value;
23504     },
23505
23506     /**
23507      * Replaces an item in the hash. If the key doesn't exist, the
23508      * {@link #add} method will be used.
23509      * @param {String} key The key of the item.
23510      * @param {Object} value The new value for the item.
23511      * @return {Object} The new value of the item.
23512      */
23513     replace: function(key, value) {
23514         var me = this,
23515             map = me.map,
23516             old;
23517
23518         if (!me.containsKey(key)) {
23519             me.add(key, value);
23520         }
23521         old = map[key];
23522         map[key] = value;
23523         me.fireEvent('replace', me, key, value, old);
23524         return value;
23525     },
23526
23527     /**
23528      * Remove an item from the hash.
23529      * @param {Object} o The value of the item to remove.
23530      * @return {Boolean} True if the item was successfully removed.
23531      */
23532     remove: function(o) {
23533         var key = this.findKey(o);
23534         if (key !== undefined) {
23535             return this.removeAtKey(key);
23536         }
23537         return false;
23538     },
23539
23540     /**
23541      * Remove an item from the hash.
23542      * @param {String} key The key to remove.
23543      * @return {Boolean} True if the item was successfully removed.
23544      */
23545     removeAtKey: function(key) {
23546         var me = this,
23547             value;
23548
23549         if (me.containsKey(key)) {
23550             value = me.map[key];
23551             delete me.map[key];
23552             --me.length;
23553             me.fireEvent('remove', me, key, value);
23554             return true;
23555         }
23556         return false;
23557     },
23558
23559     /**
23560      * Retrieves an item with a particular key.
23561      * @param {String} key The key to lookup.
23562      * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
23563      */
23564     get: function(key) {
23565         return this.map[key];
23566     },
23567
23568     /**
23569      * Removes all items from the hash.
23570      * @return {Ext.util.HashMap} this
23571      */
23572     clear: function(/* private */ initial) {
23573         var me = this;
23574         me.map = {};
23575         me.length = 0;
23576         if (initial !== true) {
23577             me.fireEvent('clear', me);
23578         }
23579         return me;
23580     },
23581
23582     /**
23583      * Checks whether a key exists in the hash.
23584      * @param {String} key The key to check for.
23585      * @return {Boolean} True if they key exists in the hash.
23586      */
23587     containsKey: function(key) {
23588         return this.map[key] !== undefined;
23589     },
23590
23591     /**
23592      * Checks whether a value exists in the hash.
23593      * @param {Object} value The value to check for.
23594      * @return {Boolean} True if the value exists in the dictionary.
23595      */
23596     contains: function(value) {
23597         return this.containsKey(this.findKey(value));
23598     },
23599
23600     /**
23601      * Return all of the keys in the hash.
23602      * @return {Array} An array of keys.
23603      */
23604     getKeys: function() {
23605         return this.getArray(true);
23606     },
23607
23608     /**
23609      * Return all of the values in the hash.
23610      * @return {Array} An array of values.
23611      */
23612     getValues: function() {
23613         return this.getArray(false);
23614     },
23615
23616     /**
23617      * Gets either the keys/values in an array from the hash.
23618      * @private
23619      * @param {Boolean} isKey True to extract the keys, otherwise, the value
23620      * @return {Array} An array of either keys/values from the hash.
23621      */
23622     getArray: function(isKey) {
23623         var arr = [],
23624             key,
23625             map = this.map;
23626         for (key in map) {
23627             if (map.hasOwnProperty(key)) {
23628                 arr.push(isKey ? key: map[key]);
23629             }
23630         }
23631         return arr;
23632     },
23633
23634     /**
23635      * Executes the specified function once for each item in the hash.
23636      * Returning false from the function will cease iteration.
23637      *
23638      * The paramaters passed to the function are:
23639      * <div class="mdetail-params"><ul>
23640      * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
23641      * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
23642      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
23643      * </ul></div>
23644      * @param {Function} fn The function to execute.
23645      * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
23646      * @return {Ext.util.HashMap} this
23647      */
23648     each: function(fn, scope) {
23649         // copy items so they may be removed during iteration.
23650         var items = Ext.apply({}, this.map),
23651             key,
23652             length = this.length;
23653
23654         scope = scope || this;
23655         for (key in items) {
23656             if (items.hasOwnProperty(key)) {
23657                 if (fn.call(scope, key, items[key], length) === false) {
23658                     break;
23659                 }
23660             }
23661         }
23662         return this;
23663     },
23664
23665     /**
23666      * Performs a shallow copy on this hash.
23667      * @return {Ext.util.HashMap} The new hash object.
23668      */
23669     clone: function() {
23670         var hash = new this.self(),
23671             map = this.map,
23672             key;
23673
23674         hash.suspendEvents();
23675         for (key in map) {
23676             if (map.hasOwnProperty(key)) {
23677                 hash.add(key, map[key]);
23678             }
23679         }
23680         hash.resumeEvents();
23681         return hash;
23682     },
23683
23684     /**
23685      * @private
23686      * Find the key for a value.
23687      * @param {Object} value The value to find.
23688      * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
23689      */
23690     findKey: function(value) {
23691         var key,
23692             map = this.map;
23693
23694         for (key in map) {
23695             if (map.hasOwnProperty(key) && map[key] === value) {
23696                 return key;
23697             }
23698         }
23699         return undefined;
23700     }
23701 });
23702
23703 /**
23704  * @class Ext.state.Manager
23705  * This is the global state manager. By default all components that are "state aware" check this class
23706  * for state information if you don't pass them a custom state provider. In order for this class
23707  * to be useful, it must be initialized with a provider when your application initializes. Example usage:
23708  <pre><code>
23709 // in your initialization function
23710 init : function(){
23711    Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
23712    var win = new Window(...);
23713    win.restoreState();
23714 }
23715  </code></pre>
23716  * This class passes on calls from components to the underlying {@link Ext.state.Provider} so that
23717  * there is a common interface that can be used without needing to refer to a specific provider instance
23718  * in every component.
23719  * @singleton
23720  * @docauthor Evan Trimboli <evan@sencha.com>
23721  */
23722 Ext.define('Ext.state.Manager', {
23723     singleton: true,
23724     requires: ['Ext.state.Provider'],
23725     constructor: function() {
23726         this.provider = Ext.create('Ext.state.Provider');
23727     },
23728     
23729     
23730     /**
23731      * Configures the default state provider for your application
23732      * @param {Ext.state.Provider} stateProvider The state provider to set
23733      */
23734     setProvider : function(stateProvider){
23735         this.provider = stateProvider;
23736     },
23737
23738     /**
23739      * Returns the current value for a key
23740      * @param {String} name The key name
23741      * @param {Object} defaultValue The default value to return if the key lookup does not match
23742      * @return {Object} The state data
23743      */
23744     get : function(key, defaultValue){
23745         return this.provider.get(key, defaultValue);
23746     },
23747
23748     /**
23749      * Sets the value for a key
23750      * @param {String} name The key name
23751      * @param {Object} value The state data
23752      */
23753      set : function(key, value){
23754         this.provider.set(key, value);
23755     },
23756
23757     /**
23758      * Clears a value from the state
23759      * @param {String} name The key name
23760      */
23761     clear : function(key){
23762         this.provider.clear(key);
23763     },
23764
23765     /**
23766      * Gets the currently configured state provider
23767      * @return {Ext.state.Provider} The state provider
23768      */
23769     getProvider : function(){
23770         return this.provider;
23771     }
23772 });
23773 /**
23774  * @class Ext.state.Stateful
23775  * A mixin for being able to save the state of an object to an underlying
23776  * {@link Ext.state.Provider}.
23777  */
23778 Ext.define('Ext.state.Stateful', {
23779
23780     /* Begin Definitions */
23781
23782    mixins: {
23783         observable: 'Ext.util.Observable'
23784     },
23785
23786     requires: ['Ext.state.Manager'],
23787
23788     /* End Definitions */
23789
23790     /**
23791      * @cfg {Boolean} stateful
23792      * <p>A flag which causes the object to attempt to restore the state of
23793      * internal properties from a saved state on startup. The object must have
23794      * a <code>{@link #stateId}</code> for state to be managed.
23795      * Auto-generated ids are not guaranteed to be stable across page loads and
23796      * cannot be relied upon to save and restore the same state for a object.<p>
23797      * <p>For state saving to work, the state manager's provider must have been
23798      * set to an implementation of {@link Ext.state.Provider} which overrides the
23799      * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
23800      * methods to save and recall name/value pairs. A built-in implementation,
23801      * {@link Ext.state.CookieProvider} is available.</p>
23802      * <p>To set the state provider for the current page:</p>
23803      * <pre><code>
23804 Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
23805     expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
23806 }));
23807      * </code></pre>
23808      * <p>A stateful object attempts to save state when one of the events
23809      * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
23810      * <p>To save state, a stateful object first serializes its state by
23811      * calling <b><code>{@link #getState}</code></b>. By default, this function does
23812      * nothing. The developer must provide an implementation which returns an
23813      * object hash which represents the restorable state of the object.</p>
23814      * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
23815      * which uses the configured {@link Ext.state.Provider} to save the object
23816      * keyed by the <code>{@link #stateId}</code>.</p>
23817      * <p>During construction, a stateful object attempts to <i>restore</i>
23818      * its state by calling {@link Ext.state.Manager#get} passing the
23819      * <code>{@link #stateId}</code></p>
23820      * <p>The resulting object is passed to <b><code>{@link #applyState}</code></b>.
23821      * The default implementation of <code>{@link #applyState}</code> simply copies
23822      * properties into the object, but a developer may override this to support
23823      * more behaviour.</p>
23824      * <p>You can perform extra processing on state save and restore by attaching
23825      * handlers to the {@link #beforestaterestore}, {@link #staterestore},
23826      * {@link #beforestatesave} and {@link #statesave} events.</p>
23827      */
23828     stateful: true,
23829
23830     /**
23831      * @cfg {String} stateId
23832      * The unique id for this object to use for state management purposes.
23833      * <p>See {@link #stateful} for an explanation of saving and restoring state.</p>
23834      */
23835
23836     /**
23837      * @cfg {String[]} stateEvents
23838      * <p>An array of events that, when fired, should trigger this object to
23839      * save its state. Defaults to none. <code>stateEvents</code> may be any type
23840      * of event supported by this object, including browser or custom events
23841      * (e.g., <tt>['click', 'customerchange']</tt>).</p>
23842      * <p>See <code>{@link #stateful}</code> for an explanation of saving and
23843      * restoring object state.</p>
23844      */
23845
23846     /**
23847      * @cfg {Number} saveDelay
23848      * A buffer to be applied if many state events are fired within a short period.
23849      */
23850     saveDelay: 100,
23851
23852     autoGenIdRe: /^((\w+-)|(ext-comp-))\d{4,}$/i,
23853
23854     constructor: function(config) {
23855         var me = this;
23856
23857         config = config || {};
23858         if (Ext.isDefined(config.stateful)) {
23859             me.stateful = config.stateful;
23860         }
23861         if (Ext.isDefined(config.saveDelay)) {
23862             me.saveDelay = config.saveDelay;
23863         }
23864         me.stateId = me.stateId || config.stateId;
23865
23866         if (!me.stateEvents) {
23867             me.stateEvents = [];
23868         }
23869         if (config.stateEvents) {
23870             me.stateEvents.concat(config.stateEvents);
23871         }
23872         this.addEvents(
23873             /**
23874              * @event beforestaterestore
23875              * Fires before the state of the object is restored. Return false from an event handler to stop the restore.
23876              * @param {Ext.state.Stateful} this
23877              * @param {Object} state The hash of state values returned from the StateProvider. If this
23878              * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
23879              * that simply copies property values into this object. The method maybe overriden to
23880              * provide custom state restoration.
23881              */
23882             'beforestaterestore',
23883
23884             /**
23885              * @event staterestore
23886              * Fires after the state of the object is restored.
23887              * @param {Ext.state.Stateful} this
23888              * @param {Object} state The hash of state values returned from the StateProvider. This is passed
23889              * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
23890              * object. The method maybe overriden to provide custom state restoration.
23891              */
23892             'staterestore',
23893
23894             /**
23895              * @event beforestatesave
23896              * Fires before the state of the object is saved to the configured state provider. Return false to stop the save.
23897              * @param {Ext.state.Stateful} this
23898              * @param {Object} state The hash of state values. This is determined by calling
23899              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
23900              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
23901              * has a null implementation.
23902              */
23903             'beforestatesave',
23904
23905             /**
23906              * @event statesave
23907              * Fires after the state of the object is saved to the configured state provider.
23908              * @param {Ext.state.Stateful} this
23909              * @param {Object} state The hash of state values. This is determined by calling
23910              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
23911              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
23912              * has a null implementation.
23913              */
23914             'statesave'
23915         );
23916         me.mixins.observable.constructor.call(me);
23917         if (me.stateful !== false) {
23918             me.initStateEvents();
23919             me.initState();
23920         }
23921     },
23922
23923     /**
23924      * Initializes any state events for this object.
23925      * @private
23926      */
23927     initStateEvents: function() {
23928         this.addStateEvents(this.stateEvents);
23929     },
23930
23931     /**
23932      * Add events that will trigger the state to be saved.
23933      * @param {String/String[]} events The event name or an array of event names.
23934      */
23935     addStateEvents: function(events){
23936         if (!Ext.isArray(events)) {
23937             events = [events];
23938         }
23939
23940         var me = this,
23941             i = 0,
23942             len = events.length;
23943
23944         for (; i < len; ++i) {
23945             me.on(events[i], me.onStateChange, me);
23946         }
23947     },
23948
23949     /**
23950      * This method is called when any of the {@link #stateEvents} are fired.
23951      * @private
23952      */
23953     onStateChange: function(){
23954         var me = this,
23955             delay = me.saveDelay;
23956
23957         if (delay > 0) {
23958             if (!me.stateTask) {
23959                 me.stateTask = Ext.create('Ext.util.DelayedTask', me.saveState, me);
23960             }
23961             me.stateTask.delay(me.saveDelay);
23962         } else {
23963             me.saveState();
23964         }
23965     },
23966
23967     /**
23968      * Saves the state of the object to the persistence store.
23969      * @private
23970      */
23971     saveState: function() {
23972         var me = this,
23973             id,
23974             state;
23975
23976         if (me.stateful !== false) {
23977             id = me.getStateId();
23978             if (id) {
23979                 state = me.getState();
23980                 if (me.fireEvent('beforestatesave', me, state) !== false) {
23981                     Ext.state.Manager.set(id, state);
23982                     me.fireEvent('statesave', me, state);
23983                 }
23984             }
23985         }
23986     },
23987
23988     /**
23989      * Gets the current state of the object. By default this function returns null,
23990      * it should be overridden in subclasses to implement methods for getting the state.
23991      * @return {Object} The current state
23992      */
23993     getState: function(){
23994         return null;
23995     },
23996
23997     /**
23998      * Applies the state to the object. This should be overridden in subclasses to do
23999      * more complex state operations. By default it applies the state properties onto
24000      * the current object.
24001      * @param {Object} state The state
24002      */
24003     applyState: function(state) {
24004         if (state) {
24005             Ext.apply(this, state);
24006         }
24007     },
24008
24009     /**
24010      * Gets the state id for this object.
24011      * @return {String} The state id, null if not found.
24012      */
24013     getStateId: function() {
24014         var me = this,
24015             id = me.stateId;
24016
24017         if (!id) {
24018             id = me.autoGenIdRe.test(String(me.id)) ? null : me.id;
24019         }
24020         return id;
24021     },
24022
24023     /**
24024      * Initializes the state of the object upon construction.
24025      * @private
24026      */
24027     initState: function(){
24028         var me = this,
24029             id = me.getStateId(),
24030             state;
24031
24032         if (me.stateful !== false) {
24033             if (id) {
24034                 state = Ext.state.Manager.get(id);
24035                 if (state) {
24036                     state = Ext.apply({}, state);
24037                     if (me.fireEvent('beforestaterestore', me, state) !== false) {
24038                         me.applyState(state);
24039                         me.fireEvent('staterestore', me, state);
24040                     }
24041                 }
24042             }
24043         }
24044     },
24045
24046     /**
24047      * Conditionally saves a single property from this object to the given state object.
24048      * The idea is to only save state which has changed from the initial state so that
24049      * current software settings do not override future software settings. Only those
24050      * values that are user-changed state should be saved.
24051      *
24052      * @param {String} propName The name of the property to save.
24053      * @param {Object} state The state object in to which to save the property.
24054      * @param {String} stateName (optional) The name to use for the property in state.
24055      * @return {Boolean} True if the property was saved, false if not.
24056      */
24057     savePropToState: function (propName, state, stateName) {
24058         var me = this,
24059             value = me[propName],
24060             config = me.initialConfig;
24061
24062         if (me.hasOwnProperty(propName)) {
24063             if (!config || config[propName] !== value) {
24064                 if (state) {
24065                     state[stateName || propName] = value;
24066                 }
24067                 return true;
24068             }
24069         }
24070         return false;
24071     },
24072
24073     savePropsToState: function (propNames, state) {
24074         var me = this;
24075         Ext.each(propNames, function (propName) {
24076             me.savePropToState(propName, state);
24077         });
24078         return state;
24079     },
24080
24081     /**
24082      * Destroys this stateful object.
24083      */
24084     destroy: function(){
24085         var task = this.stateTask;
24086         if (task) {
24087             task.cancel();
24088         }
24089         this.clearListeners();
24090
24091     }
24092
24093 });
24094
24095 /**
24096  * Base Manager class
24097  */
24098 Ext.define('Ext.AbstractManager', {
24099
24100     /* Begin Definitions */
24101
24102     requires: ['Ext.util.HashMap'],
24103
24104     /* End Definitions */
24105
24106     typeName: 'type',
24107
24108     constructor: function(config) {
24109         Ext.apply(this, config || {});
24110
24111         /**
24112          * @property {Ext.util.HashMap} all
24113          * Contains all of the items currently managed
24114          */
24115         this.all = Ext.create('Ext.util.HashMap');
24116
24117         this.types = {};
24118     },
24119
24120     /**
24121      * Returns an item by id.
24122      * For additional details see {@link Ext.util.HashMap#get}.
24123      * @param {String} id The id of the item
24124      * @return {Object} The item, undefined if not found.
24125      */
24126     get : function(id) {
24127         return this.all.get(id);
24128     },
24129
24130     /**
24131      * Registers an item to be managed
24132      * @param {Object} item The item to register
24133      */
24134     register: function(item) {
24135         var all = this.all,
24136             key = all.getKey(item);
24137             
24138         if (all.containsKey(key)) {
24139             Ext.Error.raise('Registering duplicate id "' + key + '" with this manager');
24140         }
24141         this.all.add(item);
24142     },
24143
24144     /**
24145      * Unregisters an item by removing it from this manager
24146      * @param {Object} item The item to unregister
24147      */
24148     unregister: function(item) {
24149         this.all.remove(item);
24150     },
24151
24152     /**
24153      * Registers a new item constructor, keyed by a type key.
24154      * @param {String} type The mnemonic string by which the class may be looked up.
24155      * @param {Function} cls The new instance class.
24156      */
24157     registerType : function(type, cls) {
24158         this.types[type] = cls;
24159         cls[this.typeName] = type;
24160     },
24161
24162     /**
24163      * Checks if an item type is registered.
24164      * @param {String} type The mnemonic string by which the class may be looked up
24165      * @return {Boolean} Whether the type is registered.
24166      */
24167     isRegistered : function(type){
24168         return this.types[type] !== undefined;
24169     },
24170
24171     /**
24172      * Creates and returns an instance of whatever this manager manages, based on the supplied type and
24173      * config object.
24174      * @param {Object} config The config object
24175      * @param {String} defaultType If no type is discovered in the config object, we fall back to this type
24176      * @return {Object} The instance of whatever this manager is managing
24177      */
24178     create: function(config, defaultType) {
24179         var type        = config[this.typeName] || config.type || defaultType,
24180             Constructor = this.types[type];
24181
24182         if (Constructor === undefined) {
24183             Ext.Error.raise("The '" + type + "' type has not been registered with this manager");
24184         }
24185
24186         return new Constructor(config);
24187     },
24188
24189     /**
24190      * Registers a function that will be called when an item with the specified id is added to the manager.
24191      * This will happen on instantiation.
24192      * @param {String} id The item id
24193      * @param {Function} fn The callback function. Called with a single parameter, the item.
24194      * @param {Object} scope The scope (this reference) in which the callback is executed.
24195      * Defaults to the item.
24196      */
24197     onAvailable : function(id, fn, scope){
24198         var all = this.all,
24199             item;
24200         
24201         if (all.containsKey(id)) {
24202             item = all.get(id);
24203             fn.call(scope || item, item);
24204         } else {
24205             all.on('add', function(map, key, item){
24206                 if (key == id) {
24207                     fn.call(scope || item, item);
24208                     all.un('add', fn, scope);
24209                 }
24210             });
24211         }
24212     },
24213     
24214     /**
24215      * Executes the specified function once for each item in the collection.
24216      * @param {Function} fn The function to execute.
24217      * @param {String} fn.key The key of the item
24218      * @param {Number} fn.value The value of the item
24219      * @param {Number} fn.length The total number of items in the collection
24220      * @param {Boolean} fn.return False to cease iteration.
24221      * @param {Object} scope The scope to execute in. Defaults to `this`.
24222      */
24223     each: function(fn, scope){
24224         this.all.each(fn, scope || this);    
24225     },
24226     
24227     /**
24228      * Gets the number of items in the collection.
24229      * @return {Number} The number of items in the collection.
24230      */
24231     getCount: function(){
24232         return this.all.getCount();
24233     }
24234 });
24235
24236 /**
24237  * @class Ext.ComponentManager
24238  * @extends Ext.AbstractManager
24239  * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
24240  * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
24241  * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
24242  * <p>This object also provides a registry of available Component <i>classes</i>
24243  * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
24244  * The <code>xtype</code> provides a way to avoid instantiating child Components
24245  * when creating a full, nested config object for a complete Ext page.</p>
24246  * <p>A child Component may be specified simply as a <i>config object</i>
24247  * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
24248  * needs rendering, the correct type can be looked up for lazy instantiation.</p>
24249  * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
24250  * @singleton
24251  */
24252 Ext.define('Ext.ComponentManager', {
24253     extend: 'Ext.AbstractManager',
24254     alternateClassName: 'Ext.ComponentMgr',
24255     
24256     singleton: true,
24257     
24258     typeName: 'xtype',
24259     
24260     /**
24261      * Creates a new Component from the specified config object using the
24262      * config object's xtype to determine the class to instantiate.
24263      * @param {Object} config A configuration object for the Component you wish to create.
24264      * @param {Function} defaultType (optional) The constructor to provide the default Component type if
24265      * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
24266      * @return {Ext.Component} The newly instantiated Component.
24267      */
24268     create: function(component, defaultType){
24269         if (component instanceof Ext.AbstractComponent) {
24270             return component;
24271         }
24272         else if (Ext.isString(component)) {
24273             return Ext.createByAlias('widget.' + component);
24274         }
24275         else {
24276             var type = component.xtype || defaultType,
24277                 config = component;
24278             
24279             return Ext.createByAlias('widget.' + type, config);
24280         }
24281     },
24282
24283     registerType: function(type, cls) {
24284         this.types[type] = cls;
24285         cls[this.typeName] = type;
24286         cls.prototype[this.typeName] = type;
24287     }
24288 });
24289 /**
24290  * An abstract base class which provides shared methods for Components across the Sencha product line.
24291  *
24292  * Please refer to sub class's documentation
24293  * @private
24294  */
24295 Ext.define('Ext.AbstractComponent', {
24296
24297     /* Begin Definitions */
24298     requires: [
24299         'Ext.ComponentQuery',
24300         'Ext.ComponentManager'
24301     ],
24302
24303     mixins: {
24304         observable: 'Ext.util.Observable',
24305         animate: 'Ext.util.Animate',
24306         state: 'Ext.state.Stateful'
24307     },
24308
24309     // The "uses" property specifies class which are used in an instantiated AbstractComponent.
24310     // They do *not* have to be loaded before this class may be defined - that is what "requires" is for.
24311     uses: [
24312         'Ext.PluginManager',
24313         'Ext.ComponentManager',
24314         'Ext.Element',
24315         'Ext.DomHelper',
24316         'Ext.XTemplate',
24317         'Ext.ComponentQuery',
24318         'Ext.ComponentLoader',
24319         'Ext.EventManager',
24320         'Ext.layout.Layout',
24321         'Ext.layout.component.Auto',
24322         'Ext.LoadMask',
24323         'Ext.ZIndexManager'
24324     ],
24325
24326     statics: {
24327         AUTO_ID: 1000
24328     },
24329
24330     /* End Definitions */
24331
24332     isComponent: true,
24333
24334     getAutoId: function() {
24335         return ++Ext.AbstractComponent.AUTO_ID;
24336     },
24337
24338
24339     /**
24340      * @cfg {String} id
24341      * The **unique id of this component instance.**
24342      *
24343      * It should not be necessary to use this configuration except for singleton objects in your application. Components
24344      * created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.
24345      *
24346      * Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery}
24347      * which provides selector-based searching for Sencha Components analogous to DOM querying. The {@link
24348      * Ext.container.Container Container} class contains {@link Ext.container.Container#down shortcut methods} to query
24349      * its descendant Components by selector.
24350      *
24351      * Note that this id will also be used as the element id for the containing HTML element that is rendered to the
24352      * page for this component. This allows you to write id-based CSS rules to style the specific instance of this
24353      * component uniquely, and also to select sub-elements using this component's id as the parent.
24354      *
24355      * **Note**: to avoid complications imposed by a unique id also see `{@link #itemId}`.
24356      *
24357      * **Note**: to access the container of a Component see `{@link #ownerCt}`.
24358      *
24359      * Defaults to an {@link #getId auto-assigned id}.
24360      */
24361
24362     /**
24363      * @cfg {String} itemId
24364      * An itemId can be used as an alternative way to get a reference to a component when no object reference is
24365      * available. Instead of using an `{@link #id}` with {@link Ext}.{@link Ext#getCmp getCmp}, use `itemId` with
24366      * {@link Ext.container.Container}.{@link Ext.container.Container#getComponent getComponent} which will retrieve
24367      * `itemId`'s or {@link #id}'s. Since `itemId`'s are an index to the container's internal MixedCollection, the
24368      * `itemId` is scoped locally to the container -- avoiding potential conflicts with {@link Ext.ComponentManager}
24369      * which requires a **unique** `{@link #id}`.
24370      *
24371      *     var c = new Ext.panel.Panel({ //
24372      *         {@link Ext.Component#height height}: 300,
24373      *         {@link #renderTo}: document.body,
24374      *         {@link Ext.container.Container#layout layout}: 'auto',
24375      *         {@link Ext.container.Container#items items}: [
24376      *             {
24377      *                 itemId: 'p1',
24378      *                 {@link Ext.panel.Panel#title title}: 'Panel 1',
24379      *                 {@link Ext.Component#height height}: 150
24380      *             },
24381      *             {
24382      *                 itemId: 'p2',
24383      *                 {@link Ext.panel.Panel#title title}: 'Panel 2',
24384      *                 {@link Ext.Component#height height}: 150
24385      *             }
24386      *         ]
24387      *     })
24388      *     p1 = c.{@link Ext.container.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
24389      *     p2 = p1.{@link #ownerCt}.{@link Ext.container.Container#getComponent getComponent}('p2'); // reference via a sibling
24390      *
24391      * Also see {@link #id}, `{@link Ext.container.Container#query}`, `{@link Ext.container.Container#down}` and
24392      * `{@link Ext.container.Container#child}`.
24393      *
24394      * **Note**: to access the container of an item see {@link #ownerCt}.
24395      */
24396
24397     /**
24398      * @property {Ext.Container} ownerCt
24399      * This Component's owner {@link Ext.container.Container Container} (is set automatically
24400      * when this Component is added to a Container). Read-only.
24401      *
24402      * **Note**: to access items within the Container see {@link #itemId}.
24403      */
24404
24405     /**
24406      * @property {Boolean} layoutManagedWidth
24407      * @private
24408      * Flag set by the container layout to which this Component is added.
24409      * If the layout manages this Component's width, it sets the value to 1.
24410      * If it does NOT manage the width, it sets it to 2.
24411      * If the layout MAY affect the width, but only if the owning Container has a fixed width, this is set to 0.
24412      */
24413
24414     /**
24415      * @property {Boolean} layoutManagedHeight
24416      * @private
24417      * Flag set by the container layout to which this Component is added.
24418      * If the layout manages this Component's height, it sets the value to 1.
24419      * If it does NOT manage the height, it sets it to 2.
24420      * If the layout MAY affect the height, but only if the owning Container has a fixed height, this is set to 0.
24421      */
24422
24423     /**
24424      * @cfg {String/Object} autoEl
24425      * A tag name or {@link Ext.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
24426      * encapsulate this Component.
24427      *
24428      * You do not normally need to specify this. For the base classes {@link Ext.Component} and
24429      * {@link Ext.container.Container}, this defaults to **'div'**. The more complex Sencha classes use a more
24430      * complex DOM structure specified by their own {@link #renderTpl}s.
24431      *
24432      * This is intended to allow the developer to create application-specific utility Components encapsulated by
24433      * different DOM elements. Example usage:
24434      *
24435      *     {
24436      *         xtype: 'component',
24437      *         autoEl: {
24438      *             tag: 'img',
24439      *             src: 'http://www.example.com/example.jpg'
24440      *         }
24441      *     }, {
24442      *         xtype: 'component',
24443      *         autoEl: {
24444      *             tag: 'blockquote',
24445      *             html: 'autoEl is cool!'
24446      *         }
24447      *     }, {
24448      *         xtype: 'container',
24449      *         autoEl: 'ul',
24450      *         cls: 'ux-unordered-list',
24451      *         items: {
24452      *             xtype: 'component',
24453      *             autoEl: 'li',
24454      *             html: 'First list item'
24455      *         }
24456      *     }
24457      */
24458
24459     /**
24460      * @cfg {Ext.XTemplate/String/String[]} renderTpl
24461      * An {@link Ext.XTemplate XTemplate} used to create the internal structure inside this Component's encapsulating
24462      * {@link #getEl Element}.
24463      *
24464      * You do not normally need to specify this. For the base classes {@link Ext.Component} and
24465      * {@link Ext.container.Container}, this defaults to **`null`** which means that they will be initially rendered
24466      * with no internal structure; they render their {@link #getEl Element} empty. The more specialized ExtJS and Touch
24467      * classes which use a more complex DOM structure, provide their own template definitions.
24468      *
24469      * This is intended to allow the developer to create application-specific utility Components with customized
24470      * internal structure.
24471      *
24472      * Upon rendering, any created child elements may be automatically imported into object properties using the
24473      * {@link #renderSelectors} and {@link #childEls} options.
24474      */
24475     renderTpl: null,
24476
24477     /**
24478      * @cfg {Object} renderData
24479      *
24480      * The data used by {@link #renderTpl} in addition to the following property values of the component:
24481      *
24482      * - id
24483      * - ui
24484      * - uiCls
24485      * - baseCls
24486      * - componentCls
24487      * - frame
24488      *
24489      * See {@link #renderSelectors} and {@link #childEls} for usage examples.
24490      */
24491
24492     /**
24493      * @cfg {Object} renderSelectors
24494      * An object containing properties specifying {@link Ext.DomQuery DomQuery} selectors which identify child elements
24495      * created by the render process.
24496      *
24497      * After the Component's internal structure is rendered according to the {@link #renderTpl}, this object is iterated through,
24498      * and the found Elements are added as properties to the Component using the `renderSelector` property name.
24499      *
24500      * For example, a Component which renderes a title and description into its element:
24501      *
24502      *     Ext.create('Ext.Component', {
24503      *         renderTo: Ext.getBody(),
24504      *         renderTpl: [
24505      *             '<h1 class="title">{title}</h1>',
24506      *             '<p>{desc}</p>'
24507      *         ],
24508      *         renderData: {
24509      *             title: "Error",
24510      *             desc: "Something went wrong"
24511      *         },
24512      *         renderSelectors: {
24513      *             titleEl: 'h1.title',
24514      *             descEl: 'p'
24515      *         },
24516      *         listeners: {
24517      *             afterrender: function(cmp){
24518      *                 // After rendering the component will have a titleEl and descEl properties
24519      *                 cmp.titleEl.setStyle({color: "red"});
24520      *             }
24521      *         }
24522      *     });
24523      *
24524      * For a faster, but less flexible, alternative that achieves the same end result (properties for child elements on the
24525      * Component after render), see {@link #childEls} and {@link #addChildEls}.
24526      */
24527
24528     /**
24529      * @cfg {Object[]} childEls
24530      * An array describing the child elements of the Component. Each member of the array
24531      * is an object with these properties:
24532      *
24533      * - `name` - The property name on the Component for the child element.
24534      * - `itemId` - The id to combine with the Component's id that is the id of the child element.
24535      * - `id` - The id of the child element.
24536      *
24537      * If the array member is a string, it is equivalent to `{ name: m, itemId: m }`.
24538      *
24539      * For example, a Component which renders a title and body text:
24540      *
24541      *     Ext.create('Ext.Component', {
24542      *         renderTo: Ext.getBody(),
24543      *         renderTpl: [
24544      *             '<h1 id="{id}-title">{title}</h1>',
24545      *             '<p>{msg}</p>',
24546      *         ],
24547      *         renderData: {
24548      *             title: "Error",
24549      *             msg: "Something went wrong"
24550      *         },
24551      *         childEls: ["title"],
24552      *         listeners: {
24553      *             afterrender: function(cmp){
24554      *                 // After rendering the component will have a title property
24555      *                 cmp.title.setStyle({color: "red"});
24556      *             }
24557      *         }
24558      *     });
24559      *
24560      * A more flexible, but somewhat slower, approach is {@link #renderSelectors}.
24561      */
24562
24563     /**
24564      * @cfg {String/HTMLElement/Ext.Element} renderTo
24565      * Specify the id of the element, a DOM element or an existing Element that this component will be rendered into.
24566      *
24567      * **Notes:**
24568      *
24569      * Do *not* use this option if the Component is to be a child item of a {@link Ext.container.Container Container}.
24570      * It is the responsibility of the {@link Ext.container.Container Container}'s
24571      * {@link Ext.container.Container#layout layout manager} to render and manage its child items.
24572      *
24573      * When using this config, a call to render() is not required.
24574      *
24575      * See `{@link #render}` also.
24576      */
24577
24578     /**
24579      * @cfg {Boolean} frame
24580      * Specify as `true` to have the Component inject framing elements within the Component at render time to provide a
24581      * graphical rounded frame around the Component content.
24582      *
24583      * This is only necessary when running on outdated, or non standard-compliant browsers such as Microsoft's Internet
24584      * Explorer prior to version 9 which do not support rounded corners natively.
24585      *
24586      * The extra space taken up by this framing is available from the read only property {@link #frameSize}.
24587      */
24588
24589     /**
24590      * @property {Object} frameSize
24591      * Read-only property indicating the width of any framing elements which were added within the encapsulating element
24592      * to provide graphical, rounded borders. See the {@link #frame} config.
24593      *
24594      * This is an object containing the frame width in pixels for all four sides of the Component containing the
24595      * following properties:
24596      *
24597      * @property {Number} frameSize.top The width of the top framing element in pixels.
24598      * @property {Number} frameSize.right The width of the right framing element in pixels.
24599      * @property {Number} frameSize.bottom The width of the bottom framing element in pixels.
24600      * @property {Number} frameSize.left The width of the left framing element in pixels.
24601      */
24602
24603     /**
24604      * @cfg {String/Object} componentLayout
24605      * The sizing and positioning of a Component's internal Elements is the responsibility of the Component's layout
24606      * manager which sizes a Component's internal structure in response to the Component being sized.
24607      *
24608      * Generally, developers will not use this configuration as all provided Components which need their internal
24609      * elements sizing (Such as {@link Ext.form.field.Base input fields}) come with their own componentLayout managers.
24610      *
24611      * The {@link Ext.layout.container.Auto default layout manager} will be used on instances of the base Ext.Component
24612      * class which simply sizes the Component's encapsulating element to the height and width specified in the
24613      * {@link #setSize} method.
24614      */
24615
24616     /**
24617      * @cfg {Ext.XTemplate/Ext.Template/String/String[]} tpl
24618      * An {@link Ext.Template}, {@link Ext.XTemplate} or an array of strings to form an Ext.XTemplate. Used in
24619      * conjunction with the `{@link #data}` and `{@link #tplWriteMode}` configurations.
24620      */
24621
24622     /**
24623      * @cfg {Object} data
24624      * The initial set of data to apply to the `{@link #tpl}` to update the content area of the Component.
24625      */
24626
24627     /**
24628      * @cfg {String} xtype
24629      * The `xtype` configuration option can be used to optimize Component creation and rendering. It serves as a
24630      * shortcut to the full componet name. For example, the component `Ext.button.Button` has an xtype of `button`.
24631      *
24632      * You can define your own xtype on a custom {@link Ext.Component component} by specifying the
24633      * {@link Ext.Class#alias alias} config option with a prefix of `widget`. For example:
24634      *
24635      *     Ext.define('PressMeButton', {
24636      *         extend: 'Ext.button.Button',
24637      *         alias: 'widget.pressmebutton',
24638      *         text: 'Press Me'
24639      *     })
24640      *
24641      * Any Component can be created implicitly as an object config with an xtype specified, allowing it to be
24642      * declared and passed into the rendering pipeline without actually being instantiated as an object. Not only is
24643      * rendering deferred, but the actual creation of the object itself is also deferred, saving memory and resources
24644      * until they are actually needed. In complex, nested layouts containing many Components, this can make a
24645      * noticeable improvement in performance.
24646      *
24647      *     // Explicit creation of contained Components:
24648      *     var panel = new Ext.Panel({
24649      *        ...
24650      *        items: [
24651      *           Ext.create('Ext.button.Button', {
24652      *              text: 'OK'
24653      *           })
24654      *        ]
24655      *     };
24656      *
24657      *     // Implicit creation using xtype:
24658      *     var panel = new Ext.Panel({
24659      *        ...
24660      *        items: [{
24661      *           xtype: 'button',
24662      *           text: 'OK'
24663      *        }]
24664      *     };
24665      *
24666      * In the first example, the button will always be created immediately during the panel's initialization. With
24667      * many added Components, this approach could potentially slow the rendering of the page. In the second example,
24668      * the button will not be created or rendered until the panel is actually displayed in the browser. If the panel
24669      * is never displayed (for example, if it is a tab that remains hidden) then the button will never be created and
24670      * will never consume any resources whatsoever.
24671      */
24672
24673     /**
24674      * @cfg {String} tplWriteMode
24675      * The Ext.(X)Template method to use when updating the content area of the Component.
24676      * See `{@link Ext.XTemplate#overwrite}` for information on default mode.
24677      */
24678     tplWriteMode: 'overwrite',
24679
24680     /**
24681      * @cfg {String} [baseCls='x-component']
24682      * The base CSS class to apply to this components's element. This will also be prepended to elements within this
24683      * component like Panel's body will get a class x-panel-body. This means that if you create a subclass of Panel, and
24684      * you want it to get all the Panels styling for the element and the body, you leave the baseCls x-panel and use
24685      * componentCls to add specific styling for this component.
24686      */
24687     baseCls: Ext.baseCSSPrefix + 'component',
24688
24689     /**
24690      * @cfg {String} componentCls
24691      * CSS Class to be added to a components root level element to give distinction to it via styling.
24692      */
24693
24694     /**
24695      * @cfg {String} [cls='']
24696      * An optional extra CSS class that will be added to this component's Element. This can be useful
24697      * for adding customized styles to the component or any of its children using standard CSS rules.
24698      */
24699
24700     /**
24701      * @cfg {String} [overCls='']
24702      * An optional extra CSS class that will be added to this component's Element when the mouse moves over the Element,
24703      * and removed when the mouse moves out. This can be useful for adding customized 'active' or 'hover' styles to the
24704      * component or any of its children using standard CSS rules.
24705      */
24706
24707     /**
24708      * @cfg {String} [disabledCls='x-item-disabled']
24709      * CSS class to add when the Component is disabled. Defaults to 'x-item-disabled'.
24710      */
24711     disabledCls: Ext.baseCSSPrefix + 'item-disabled',
24712
24713     /**
24714      * @cfg {String/String[]} ui
24715      * A set style for a component. Can be a string or an Array of multiple strings (UIs)
24716      */
24717     ui: 'default',
24718
24719     /**
24720      * @cfg {String[]} uiCls
24721      * An array of of classNames which are currently applied to this component
24722      * @private
24723      */
24724     uiCls: [],
24725
24726     /**
24727      * @cfg {String} style
24728      * A custom style specification to be applied to this component's Element. Should be a valid argument to
24729      * {@link Ext.Element#applyStyles}.
24730      *
24731      *     new Ext.panel.Panel({
24732      *         title: 'Some Title',
24733      *         renderTo: Ext.getBody(),
24734      *         width: 400, height: 300,
24735      *         layout: 'form',
24736      *         items: [{
24737      *             xtype: 'textarea',
24738      *             style: {
24739      *                 width: '95%',
24740      *                 marginBottom: '10px'
24741      *             }
24742      *         },
24743      *         new Ext.button.Button({
24744      *             text: 'Send',
24745      *             minWidth: '100',
24746      *             style: {
24747      *                 marginBottom: '10px'
24748      *             }
24749      *         })
24750      *         ]
24751      *     });
24752      */
24753
24754     /**
24755      * @cfg {Number} width
24756      * The width of this component in pixels.
24757      */
24758
24759     /**
24760      * @cfg {Number} height
24761      * The height of this component in pixels.
24762      */
24763
24764     /**
24765      * @cfg {Number/String} border
24766      * Specifies the border for this component. The border can be a single numeric value to apply to all sides or it can
24767      * be a CSS style specification for each style, for example: '10 5 3 10'.
24768      */
24769
24770     /**
24771      * @cfg {Number/String} padding
24772      * Specifies the padding for this component. The padding can be a single numeric value to apply to all sides or it
24773      * can be a CSS style specification for each style, for example: '10 5 3 10'.
24774      */
24775
24776     /**
24777      * @cfg {Number/String} margin
24778      * Specifies the margin for this component. The margin can be a single numeric value to apply to all sides or it can
24779      * be a CSS style specification for each style, for example: '10 5 3 10'.
24780      */
24781
24782     /**
24783      * @cfg {Boolean} hidden
24784      * True to hide the component.
24785      */
24786     hidden: false,
24787
24788     /**
24789      * @cfg {Boolean} disabled
24790      * True to disable the component.
24791      */
24792     disabled: false,
24793
24794     /**
24795      * @cfg {Boolean} [draggable=false]
24796      * Allows the component to be dragged.
24797      */
24798
24799     /**
24800      * @property {Boolean} draggable
24801      * Read-only property indicating whether or not the component can be dragged
24802      */
24803     draggable: false,
24804
24805     /**
24806      * @cfg {Boolean} floating
24807      * Create the Component as a floating and use absolute positioning.
24808      *
24809      * 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
24810      * by the global {@link Ext.WindowManager WindowManager}.
24811      *
24812      * 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
24813      * ZIndexManager instance to manage its descendant floaters. If no floating ancestor can be found, the global WindowManager will be used.
24814      *
24815      * When a floating Component which has a ZindexManager managing descendant floaters is destroyed, those descendant floaters will also be destroyed.
24816      */
24817     floating: false,
24818
24819     /**
24820      * @cfg {String} hideMode
24821      * A String which specifies how this Component's encapsulating DOM element will be hidden. Values may be:
24822      *
24823      *   - `'display'` : The Component will be hidden using the `display: none` style.
24824      *   - `'visibility'` : The Component will be hidden using the `visibility: hidden` style.
24825      *   - `'offsets'` : The Component will be hidden by absolutely positioning it out of the visible area of the document.
24826      *     This is useful when a hidden Component must maintain measurable dimensions. Hiding using `display` results in a
24827      *     Component having zero dimensions.
24828      */
24829     hideMode: 'display',
24830
24831     /**
24832      * @cfg {String} contentEl
24833      * Specify an existing HTML element, or the `id` of an existing HTML element to use as the content for this component.
24834      *
24835      * This config option is used to take an existing HTML element and place it in the layout element of a new component
24836      * (it simply moves the specified DOM element _after the Component is rendered_ to use as the content.
24837      *
24838      * **Notes:**
24839      *
24840      * The specified HTML element is appended to the layout element of the component _after any configured
24841      * {@link #html HTML} has been inserted_, and so the document will not contain this element at the time
24842      * the {@link #render} event is fired.
24843      *
24844      * The specified HTML element used will not participate in any **`{@link Ext.container.Container#layout layout}`**
24845      * scheme that the Component may use. It is just HTML. Layouts operate on child
24846      * **`{@link Ext.container.Container#items items}`**.
24847      *
24848      * Add either the `x-hidden` or the `x-hide-display` CSS class to prevent a brief flicker of the content before it
24849      * is rendered to the panel.
24850      */
24851
24852     /**
24853      * @cfg {String/Object} [html='']
24854      * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the layout element content.
24855      * The HTML content is added after the component is rendered, so the document will not contain this HTML at the time
24856      * the {@link #render} event is fired. This content is inserted into the body _before_ any configured {@link #contentEl}
24857      * is appended.
24858      */
24859
24860     /**
24861      * @cfg {Boolean} styleHtmlContent
24862      * True to automatically style the html inside the content target of this component (body for panels).
24863      */
24864     styleHtmlContent: false,
24865
24866     /**
24867      * @cfg {String} [styleHtmlCls='x-html']
24868      * The class that is added to the content target when you set styleHtmlContent to true.
24869      */
24870     styleHtmlCls: Ext.baseCSSPrefix + 'html',
24871
24872     /**
24873      * @cfg {Number} minHeight
24874      * The minimum value in pixels which this Component will set its height to.
24875      *
24876      * **Warning:** This will override any size management applied by layout managers.
24877      */
24878     /**
24879      * @cfg {Number} minWidth
24880      * The minimum value in pixels which this Component will set its width to.
24881      *
24882      * **Warning:** This will override any size management applied by layout managers.
24883      */
24884     /**
24885      * @cfg {Number} maxHeight
24886      * The maximum value in pixels which this Component will set its height to.
24887      *
24888      * **Warning:** This will override any size management applied by layout managers.
24889      */
24890     /**
24891      * @cfg {Number} maxWidth
24892      * The maximum value in pixels which this Component will set its width to.
24893      *
24894      * **Warning:** This will override any size management applied by layout managers.
24895      */
24896
24897     /**
24898      * @cfg {Ext.ComponentLoader/Object} loader
24899      * A configuration object or an instance of a {@link Ext.ComponentLoader} to load remote content for this Component.
24900      */
24901
24902     /**
24903      * @cfg {Boolean} autoShow
24904      * True to automatically show the component upon creation. This config option may only be used for
24905      * {@link #floating} components or components that use {@link #autoRender}. Defaults to false.
24906      */
24907     autoShow: false,
24908
24909     /**
24910      * @cfg {Boolean/String/HTMLElement/Ext.Element} autoRender
24911      * This config is intended mainly for non-{@link #floating} Components which may or may not be shown. Instead of using
24912      * {@link #renderTo} in the configuration, and rendering upon construction, this allows a Component to render itself
24913      * upon first _{@link #show}_. If {@link #floating} is true, the value of this config is omited as if it is `true`.
24914      *
24915      * Specify as `true` to have this Component render to the document body upon first show.
24916      *
24917      * Specify as an element, or the ID of an element to have this Component render to a specific element upon first
24918      * show.
24919      *
24920      * **This defaults to `true` for the {@link Ext.window.Window Window} class.**
24921      */
24922     autoRender: false,
24923
24924     needsLayout: false,
24925
24926     // @private
24927     allowDomMove: true,
24928
24929     /**
24930      * @cfg {Object/Object[]} plugins
24931      * An object or array of objects that will provide custom functionality for this component. The only requirement for
24932      * a valid plugin is that it contain an init method that accepts a reference of type Ext.Component. When a component
24933      * is created, if any plugins are available, the component will call the init method on each plugin, passing a
24934      * reference to itself. Each plugin can then call methods or respond to events on the component as needed to provide
24935      * its functionality.
24936      */
24937
24938     /**
24939      * @property {Boolean} rendered
24940      * Read-only property indicating whether or not the component has been rendered.
24941      */
24942     rendered: false,
24943
24944     /**
24945      * @property {Number} componentLayoutCounter
24946      * @private
24947      * The number of component layout calls made on this object.
24948      */
24949     componentLayoutCounter: 0,
24950
24951     weight: 0,
24952
24953     trimRe: /^\s+|\s+$/g,
24954     spacesRe: /\s+/,
24955
24956
24957     /**
24958      * @property {Boolean} maskOnDisable
24959      * This is an internal flag that you use when creating custom components. By default this is set to true which means
24960      * that every component gets a mask when its disabled. Components like FieldContainer, FieldSet, Field, Button, Tab
24961      * override this property to false since they want to implement custom disable logic.
24962      */
24963     maskOnDisable: true,
24964
24965     /**
24966      * Creates new Component.
24967      * @param {Object} config  (optional) Config object.
24968      */
24969     constructor : function(config) {
24970         var me = this,
24971             i, len;
24972
24973         config = config || {};
24974         me.initialConfig = config;
24975         Ext.apply(me, config);
24976
24977         me.addEvents(
24978             /**
24979              * @event beforeactivate
24980              * Fires before a Component has been visually activated. Returning false from an event listener can prevent
24981              * the activate from occurring.
24982              * @param {Ext.Component} this
24983              */
24984             'beforeactivate',
24985             /**
24986              * @event activate
24987              * Fires after a Component has been visually activated.
24988              * @param {Ext.Component} this
24989              */
24990             'activate',
24991             /**
24992              * @event beforedeactivate
24993              * Fires before a Component has been visually deactivated. Returning false from an event listener can
24994              * prevent the deactivate from occurring.
24995              * @param {Ext.Component} this
24996              */
24997             'beforedeactivate',
24998             /**
24999              * @event deactivate
25000              * Fires after a Component has been visually deactivated.
25001              * @param {Ext.Component} this
25002              */
25003             'deactivate',
25004             /**
25005              * @event added
25006              * Fires after a Component had been added to a Container.
25007              * @param {Ext.Component} this
25008              * @param {Ext.container.Container} container Parent Container
25009              * @param {Number} pos position of Component
25010              */
25011             'added',
25012             /**
25013              * @event disable
25014              * Fires after the component is disabled.
25015              * @param {Ext.Component} this
25016              */
25017             'disable',
25018             /**
25019              * @event enable
25020              * Fires after the component is enabled.
25021              * @param {Ext.Component} this
25022              */
25023             'enable',
25024             /**
25025              * @event beforeshow
25026              * Fires before the component is shown when calling the {@link #show} method. Return false from an event
25027              * handler to stop the show.
25028              * @param {Ext.Component} this
25029              */
25030             'beforeshow',
25031             /**
25032              * @event show
25033              * Fires after the component is shown when calling the {@link #show} method.
25034              * @param {Ext.Component} this
25035              */
25036             'show',
25037             /**
25038              * @event beforehide
25039              * Fires before the component is hidden when calling the {@link #hide} method. Return false from an event
25040              * handler to stop the hide.
25041              * @param {Ext.Component} this
25042              */
25043             'beforehide',
25044             /**
25045              * @event hide
25046              * Fires after the component is hidden. Fires after the component is hidden when calling the {@link #hide}
25047              * method.
25048              * @param {Ext.Component} this
25049              */
25050             'hide',
25051             /**
25052              * @event removed
25053              * Fires when a component is removed from an Ext.container.Container
25054              * @param {Ext.Component} this
25055              * @param {Ext.container.Container} ownerCt Container which holds the component
25056              */
25057             'removed',
25058             /**
25059              * @event beforerender
25060              * Fires before the component is {@link #rendered}. Return false from an event handler to stop the
25061              * {@link #render}.
25062              * @param {Ext.Component} this
25063              */
25064             'beforerender',
25065             /**
25066              * @event render
25067              * Fires after the component markup is {@link #rendered}.
25068              * @param {Ext.Component} this
25069              */
25070             'render',
25071             /**
25072              * @event afterrender
25073              * Fires after the component rendering is finished.
25074              *
25075              * The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed by any
25076              * afterRender method defined for the Component.
25077              * @param {Ext.Component} this
25078              */
25079             'afterrender',
25080             /**
25081              * @event beforedestroy
25082              * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the
25083              * {@link #destroy}.
25084              * @param {Ext.Component} this
25085              */
25086             'beforedestroy',
25087             /**
25088              * @event destroy
25089              * Fires after the component is {@link #destroy}ed.
25090              * @param {Ext.Component} this
25091              */
25092             'destroy',
25093             /**
25094              * @event resize
25095              * Fires after the component is resized.
25096              * @param {Ext.Component} this
25097              * @param {Number} adjWidth The box-adjusted width that was set
25098              * @param {Number} adjHeight The box-adjusted height that was set
25099              */
25100             'resize',
25101             /**
25102              * @event move
25103              * Fires after the component is moved.
25104              * @param {Ext.Component} this
25105              * @param {Number} x The new x position
25106              * @param {Number} y The new y position
25107              */
25108             'move'
25109         );
25110
25111         me.getId();
25112
25113         me.mons = [];
25114         me.additionalCls = [];
25115         me.renderData = me.renderData || {};
25116         me.renderSelectors = me.renderSelectors || {};
25117
25118         if (me.plugins) {
25119             me.plugins = [].concat(me.plugins);
25120             me.constructPlugins();
25121         }
25122
25123         me.initComponent();
25124
25125         // ititComponent gets a chance to change the id property before registering
25126         Ext.ComponentManager.register(me);
25127
25128         // Dont pass the config so that it is not applied to 'this' again
25129         me.mixins.observable.constructor.call(me);
25130         me.mixins.state.constructor.call(me, config);
25131
25132         // Save state on resize.
25133         this.addStateEvents('resize');
25134
25135         // Move this into Observable?
25136         if (me.plugins) {
25137             me.plugins = [].concat(me.plugins);
25138             for (i = 0, len = me.plugins.length; i < len; i++) {
25139                 me.plugins[i] = me.initPlugin(me.plugins[i]);
25140             }
25141         }
25142
25143         me.loader = me.getLoader();
25144
25145         if (me.renderTo) {
25146             me.render(me.renderTo);
25147             // EXTJSIV-1935 - should be a way to do afterShow or something, but that
25148             // won't work. Likewise, rendering hidden and then showing (w/autoShow) has
25149             // implications to afterRender so we cannot do that.
25150         }
25151
25152         if (me.autoShow) {
25153             me.show();
25154         }
25155
25156         if (Ext.isDefined(me.disabledClass)) {
25157             if (Ext.isDefined(Ext.global.console)) {
25158                 Ext.global.console.warn('Ext.Component: disabledClass has been deprecated. Please use disabledCls.');
25159             }
25160             me.disabledCls = me.disabledClass;
25161             delete me.disabledClass;
25162         }
25163     },
25164
25165     initComponent: function () {
25166         // This is called again here to allow derived classes to add plugin configs to the
25167         // plugins array before calling down to this, the base initComponent.
25168         this.constructPlugins();
25169     },
25170
25171     /**
25172      * The supplied default state gathering method for the AbstractComponent class.
25173      *
25174      * This method returns dimension settings such as `flex`, `anchor`, `width` and `height` along with `collapsed`
25175      * state.
25176      *
25177      * Subclasses which implement more complex state should call the superclass's implementation, and apply their state
25178      * to the result if this basic state is to be saved.
25179      *
25180      * Note that Component state will only be saved if the Component has a {@link #stateId} and there as a StateProvider
25181      * configured for the document.
25182      *
25183      * @return {Object}
25184      */
25185     getState: function() {
25186         var me = this,
25187             layout = me.ownerCt ? (me.shadowOwnerCt || me.ownerCt).getLayout() : null,
25188             state = {
25189                 collapsed: me.collapsed
25190             },
25191             width = me.width,
25192             height = me.height,
25193             cm = me.collapseMemento,
25194             anchors;
25195
25196         // If a Panel-local collapse has taken place, use remembered values as the dimensions.
25197         // TODO: remove this coupling with Panel's privates! All collapse/expand logic should be refactored into one place.
25198         if (me.collapsed && cm) {
25199             if (Ext.isDefined(cm.data.width)) {
25200                 width = cm.width;
25201             }
25202             if (Ext.isDefined(cm.data.height)) {
25203                 height = cm.height;
25204             }
25205         }
25206
25207         // If we have flex, only store the perpendicular dimension.
25208         if (layout && me.flex) {
25209             state.flex = me.flex;
25210             if (layout.perpendicularPrefix) {
25211                 state[layout.perpendicularPrefix] = me['get' + layout.perpendicularPrefixCap]();
25212             } else {
25213                 if (Ext.isDefined(Ext.global.console)) {
25214                     Ext.global.console.warn('Ext.Component: Specified a flex value on a component not inside a Box layout');
25215                 }
25216             }
25217         }
25218         // If we have anchor, only store dimensions which are *not* being anchored
25219         else if (layout && me.anchor) {
25220             state.anchor = me.anchor;
25221             anchors = me.anchor.split(' ').concat(null);
25222             if (!anchors[0]) {
25223                 if (me.width) {
25224                     state.width = width;
25225                 }
25226             }
25227             if (!anchors[1]) {
25228                 if (me.height) {
25229                     state.height = height;
25230                 }
25231             }
25232         }
25233         // Store dimensions.
25234         else {
25235             if (me.width) {
25236                 state.width = width;
25237             }
25238             if (me.height) {
25239                 state.height = height;
25240             }
25241         }
25242
25243         // Don't save dimensions if they are unchanged from the original configuration.
25244         if (state.width == me.initialConfig.width) {
25245             delete state.width;
25246         }
25247         if (state.height == me.initialConfig.height) {
25248             delete state.height;
25249         }
25250
25251         // If a Box layout was managing the perpendicular dimension, don't save that dimension
25252         if (layout && layout.align && (layout.align.indexOf('stretch') !== -1)) {
25253             delete state[layout.perpendicularPrefix];
25254         }
25255         return state;
25256     },
25257
25258     show: Ext.emptyFn,
25259
25260     animate: function(animObj) {
25261         var me = this,
25262             to;
25263
25264         animObj = animObj || {};
25265         to = animObj.to || {};
25266
25267         if (Ext.fx.Manager.hasFxBlock(me.id)) {
25268             return me;
25269         }
25270         // Special processing for animating Component dimensions.
25271         if (!animObj.dynamic && (to.height || to.width)) {
25272             var curWidth = me.getWidth(),
25273                 w = curWidth,
25274                 curHeight = me.getHeight(),
25275                 h = curHeight,
25276                 needsResize = false;
25277
25278             if (to.height && to.height > curHeight) {
25279                 h = to.height;
25280                 needsResize = true;
25281             }
25282             if (to.width && to.width > curWidth) {
25283                 w = to.width;
25284                 needsResize = true;
25285             }
25286
25287             // If any dimensions are being increased, we must resize the internal structure
25288             // of the Component, but then clip it by sizing its encapsulating element back to original dimensions.
25289             // The animation will then progressively reveal the larger content.
25290             if (needsResize) {
25291                 var clearWidth = !Ext.isNumber(me.width),
25292                     clearHeight = !Ext.isNumber(me.height);
25293
25294                 me.componentLayout.childrenChanged = true;
25295                 me.setSize(w, h, me.ownerCt);
25296                 me.el.setSize(curWidth, curHeight);
25297                 if (clearWidth) {
25298                     delete me.width;
25299                 }
25300                 if (clearHeight) {
25301                     delete me.height;
25302                 }
25303             }
25304         }
25305         return me.mixins.animate.animate.apply(me, arguments);
25306     },
25307
25308     /**
25309      * This method finds the topmost active layout who's processing will eventually determine the size and position of
25310      * this Component.
25311      *
25312      * This method is useful when dynamically adding Components into Containers, and some processing must take place
25313      * after the final sizing and positioning of the Component has been performed.
25314      *
25315      * @return {Ext.Component}
25316      */
25317     findLayoutController: function() {
25318         return this.findParentBy(function(c) {
25319             // Return true if we are at the root of the Container tree
25320             // or this Container's layout is busy but the next one up is not.
25321             return !c.ownerCt || (c.layout.layoutBusy && !c.ownerCt.layout.layoutBusy);
25322         });
25323     },
25324
25325     onShow : function() {
25326         // Layout if needed
25327         var needsLayout = this.needsLayout;
25328         if (Ext.isObject(needsLayout)) {
25329             this.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
25330         }
25331     },
25332
25333     constructPlugin: function(plugin) {
25334         if (plugin.ptype && typeof plugin.init != 'function') {
25335             plugin.cmp = this;
25336             plugin = Ext.PluginManager.create(plugin);
25337         }
25338         else if (typeof plugin == 'string') {
25339             plugin = Ext.PluginManager.create({
25340                 ptype: plugin,
25341                 cmp: this
25342             });
25343         }
25344         return plugin;
25345     },
25346
25347     /**
25348      * Ensures that the plugins array contains fully constructed plugin instances. This converts any configs into their
25349      * appropriate instances.
25350      */
25351     constructPlugins: function() {
25352         var me = this,
25353             plugins = me.plugins,
25354             i, len;
25355
25356         if (plugins) {
25357             for (i = 0, len = plugins.length; i < len; i++) {
25358                 // this just returns already-constructed plugin instances...
25359                 plugins[i] = me.constructPlugin(plugins[i]);
25360             }
25361         }
25362     },
25363
25364     // @private
25365     initPlugin : function(plugin) {
25366         plugin.init(this);
25367
25368         return plugin;
25369     },
25370
25371     /**
25372      * Handles autoRender. Floating Components may have an ownerCt. If they are asking to be constrained, constrain them
25373      * within that ownerCt, and have their z-index managed locally. Floating Components are always rendered to
25374      * document.body
25375      */
25376     doAutoRender: function() {
25377         var me = this;
25378         if (me.floating) {
25379             me.render(document.body);
25380         } else {
25381             me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender);
25382         }
25383     },
25384
25385     // @private
25386     render : function(container, position) {
25387         var me = this;
25388
25389         if (!me.rendered && me.fireEvent('beforerender', me) !== false) {
25390
25391             // Flag set during the render process.
25392             // It can be used to inhibit event-driven layout calls during the render phase
25393             me.rendering = true;
25394
25395             // If this.el is defined, we want to make sure we are dealing with
25396             // an Ext Element.
25397             if (me.el) {
25398                 me.el = Ext.get(me.el);
25399             }
25400
25401             // Perform render-time processing for floating Components
25402             if (me.floating) {
25403                 me.onFloatRender();
25404             }
25405
25406             container = me.initContainer(container);
25407
25408             me.onRender(container, position);
25409
25410             // Tell the encapsulating element to hide itself in the way the Component is configured to hide
25411             // This means DISPLAY, VISIBILITY or OFFSETS.
25412             me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
25413
25414             if (me.overCls) {
25415                 me.el.hover(me.addOverCls, me.removeOverCls, me);
25416             }
25417
25418             me.fireEvent('render', me);
25419
25420             me.initContent();
25421
25422             me.afterRender(container);
25423             me.fireEvent('afterrender', me);
25424
25425             me.initEvents();
25426
25427             if (me.hidden) {
25428                 // Hiding during the render process should not perform any ancillary
25429                 // actions that the full hide process does; It is not hiding, it begins in a hidden state.'
25430                 // So just make the element hidden according to the configured hideMode
25431                 me.el.hide();
25432             }
25433
25434             if (me.disabled) {
25435                 // pass silent so the event doesn't fire the first time.
25436                 me.disable(true);
25437             }
25438
25439             // Delete the flag once the rendering is done.
25440             delete me.rendering;
25441         }
25442         return me;
25443     },
25444
25445     // @private
25446     onRender : function(container, position) {
25447         var me = this,
25448             el = me.el,
25449             styles = me.initStyles(),
25450             renderTpl, renderData, i;
25451
25452         position = me.getInsertPosition(position);
25453
25454         if (!el) {
25455             if (position) {
25456                 el = Ext.DomHelper.insertBefore(position, me.getElConfig(), true);
25457             }
25458             else {
25459                 el = Ext.DomHelper.append(container, me.getElConfig(), true);
25460             }
25461         }
25462         else if (me.allowDomMove !== false) {
25463             if (position) {
25464                 container.dom.insertBefore(el.dom, position);
25465             } else {
25466                 container.dom.appendChild(el.dom);
25467             }
25468         }
25469
25470         if (Ext.scopeResetCSS && !me.ownerCt) {
25471             // If this component's el is the body element, we add the reset class to the html tag
25472             if (el.dom == Ext.getBody().dom) {
25473                 el.parent().addCls(Ext.baseCSSPrefix + 'reset');
25474             }
25475             else {
25476                 // Else we wrap this element in an element that adds the reset class.
25477                 me.resetEl = el.wrap({
25478                     cls: Ext.baseCSSPrefix + 'reset'
25479                 });
25480             }
25481         }
25482
25483         me.setUI(me.ui);
25484
25485         el.addCls(me.initCls());
25486         el.setStyle(styles);
25487
25488         // Here we check if the component has a height set through style or css.
25489         // If it does then we set the this.height to that value and it won't be
25490         // considered an auto height component
25491         // if (this.height === undefined) {
25492         //     var height = el.getHeight();
25493         //     // This hopefully means that the panel has an explicit height set in style or css
25494         //     if (height - el.getPadding('tb') - el.getBorderWidth('tb') > 0) {
25495         //         this.height = height;
25496         //     }
25497         // }
25498
25499         me.el = el;
25500
25501         me.initFrame();
25502
25503         renderTpl = me.initRenderTpl();
25504         if (renderTpl) {
25505             renderData = me.initRenderData();
25506             renderTpl.append(me.getTargetEl(), renderData);
25507         }
25508
25509         me.applyRenderSelectors();
25510
25511         me.rendered = true;
25512     },
25513
25514     // @private
25515     afterRender : function() {
25516         var me = this,
25517             pos,
25518             xy;
25519
25520         me.getComponentLayout();
25521
25522         // Set the size if a size is configured, or if this is the outermost Container.
25523         // Also, if this is a collapsed Panel, it needs an initial component layout
25524         // to lay out its header so that it can have a height determined.
25525         if (me.collapsed || (!me.ownerCt || (me.height || me.width))) {
25526             me.setSize(me.width, me.height);
25527         } else {
25528             // It is expected that child items be rendered before this method returns and
25529             // the afterrender event fires. Since we aren't going to do the layout now, we
25530             // must render the child items. This is handled implicitly above in the layout
25531             // caused by setSize.
25532             me.renderChildren();
25533         }
25534
25535         // For floaters, calculate x and y if they aren't defined by aligning
25536         // the sized element to the center of either the container or the ownerCt
25537         if (me.floating && (me.x === undefined || me.y === undefined)) {
25538             if (me.floatParent) {
25539                 xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c');
25540                 pos = me.floatParent.getTargetEl().translatePoints(xy[0], xy[1]);
25541             } else {
25542                 xy = me.el.getAlignToXY(me.container, 'c-c');
25543                 pos = me.container.translatePoints(xy[0], xy[1]);
25544             }
25545             me.x = me.x === undefined ? pos.left: me.x;
25546             me.y = me.y === undefined ? pos.top: me.y;
25547         }
25548
25549         if (Ext.isDefined(me.x) || Ext.isDefined(me.y)) {
25550             me.setPosition(me.x, me.y);
25551         }
25552
25553         if (me.styleHtmlContent) {
25554             me.getTargetEl().addCls(me.styleHtmlCls);
25555         }
25556     },
25557
25558     /**
25559      * @private
25560      * Called by Component#doAutoRender
25561      *
25562      * Register a Container configured `floating: true` with this Component's {@link Ext.ZIndexManager ZIndexManager}.
25563      *
25564      * Components added in ths way will not participate in any layout, but will be rendered
25565      * upon first show in the way that {@link Ext.window.Window Window}s are.
25566      */
25567     registerFloatingItem: function(cmp) {
25568         var me = this;
25569         if (!me.floatingItems) {
25570             me.floatingItems = Ext.create('Ext.ZIndexManager', me);
25571         }
25572         me.floatingItems.register(cmp);
25573     },
25574
25575     renderChildren: function () {
25576         var me = this,
25577             layout = me.getComponentLayout();
25578
25579         me.suspendLayout = true;
25580         layout.renderChildren();
25581         delete me.suspendLayout;
25582     },
25583
25584     frameCls: Ext.baseCSSPrefix + 'frame',
25585
25586     frameIdRegex: /[-]frame\d+[TMB][LCR]$/,
25587
25588     frameElementCls: {
25589         tl: [],
25590         tc: [],
25591         tr: [],
25592         ml: [],
25593         mc: [],
25594         mr: [],
25595         bl: [],
25596         bc: [],
25597         br: []
25598     },
25599
25600     frameTpl: [
25601         '<tpl if="top">',
25602             '<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>',
25603                 '<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>',
25604                     '<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>',
25605                 '<tpl if="right"></div></tpl>',
25606             '<tpl if="left"></div></tpl>',
25607         '</tpl>',
25608         '<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>',
25609             '<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>',
25610                 '<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>',
25611             '<tpl if="right"></div></tpl>',
25612         '<tpl if="left"></div></tpl>',
25613         '<tpl if="bottom">',
25614             '<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>',
25615                 '<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>',
25616                     '<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>',
25617                 '<tpl if="right"></div></tpl>',
25618             '<tpl if="left"></div></tpl>',
25619         '</tpl>'
25620     ],
25621
25622     frameTableTpl: [
25623         '<table><tbody>',
25624             '<tpl if="top">',
25625                 '<tr>',
25626                     '<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>',
25627                     '<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>',
25628                     '<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>',
25629                 '</tr>',
25630             '</tpl>',
25631             '<tr>',
25632                 '<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>',
25633                 '<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>',
25634                 '<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>',
25635             '</tr>',
25636             '<tpl if="bottom">',
25637                 '<tr>',
25638                     '<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>',
25639                     '<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>',
25640                     '<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>',
25641                 '</tr>',
25642             '</tpl>',
25643         '</tbody></table>'
25644     ],
25645
25646     /**
25647      * @private
25648      */
25649     initFrame : function() {
25650         if (Ext.supports.CSS3BorderRadius) {
25651             return false;
25652         }
25653
25654         var me = this,
25655             frameInfo = me.getFrameInfo(),
25656             frameWidth = frameInfo.width,
25657             frameTpl = me.getFrameTpl(frameInfo.table),
25658             frameGenId;
25659
25660         if (me.frame) {
25661             // since we render id's into the markup and id's NEED to be unique, we have a
25662             // simple strategy for numbering their generations.
25663             me.frameGenId = frameGenId = (me.frameGenId || 0) + 1;
25664             frameGenId = me.id + '-frame' + frameGenId;
25665
25666             // Here we render the frameTpl to this component. This inserts the 9point div or the table framing.
25667             frameTpl.insertFirst(me.el, Ext.apply({}, {
25668                 fgid:       frameGenId,
25669                 ui:         me.ui,
25670                 uiCls:      me.uiCls,
25671                 frameCls:   me.frameCls,
25672                 baseCls:    me.baseCls,
25673                 frameWidth: frameWidth,
25674                 top:        !!frameInfo.top,
25675                 left:       !!frameInfo.left,
25676                 right:      !!frameInfo.right,
25677                 bottom:     !!frameInfo.bottom
25678             }, me.getFramePositions(frameInfo)));
25679
25680             // The frameBody is returned in getTargetEl, so that layouts render items to the correct target.=
25681             me.frameBody = me.el.down('.' + me.frameCls + '-mc');
25682
25683             // Clean out the childEls for the old frame elements (the majority of the els)
25684             me.removeChildEls(function (c) {
25685                 return c.id && me.frameIdRegex.test(c.id);
25686             });
25687
25688             // Add the childEls for each of the new frame elements
25689             Ext.each(['TL','TC','TR','ML','MC','MR','BL','BC','BR'], function (suffix) {
25690                 me.childEls.push({ name: 'frame' + suffix, id: frameGenId + suffix });
25691             });
25692         }
25693     },
25694
25695     updateFrame: function() {
25696         if (Ext.supports.CSS3BorderRadius) {
25697             return false;
25698         }
25699
25700         var me = this,
25701             wasTable = this.frameSize && this.frameSize.table,
25702             oldFrameTL = this.frameTL,
25703             oldFrameBL = this.frameBL,
25704             oldFrameML = this.frameML,
25705             oldFrameMC = this.frameMC,
25706             newMCClassName;
25707
25708         this.initFrame();
25709
25710         if (oldFrameMC) {
25711             if (me.frame) {
25712                 // Reapply render selectors
25713                 delete me.frameTL;
25714                 delete me.frameTC;
25715                 delete me.frameTR;
25716                 delete me.frameML;
25717                 delete me.frameMC;
25718                 delete me.frameMR;
25719                 delete me.frameBL;
25720                 delete me.frameBC;
25721                 delete me.frameBR;
25722                 this.applyRenderSelectors();
25723
25724                 // Store the class names set on the new mc
25725                 newMCClassName = this.frameMC.dom.className;
25726
25727                 // Replace the new mc with the old mc
25728                 oldFrameMC.insertAfter(this.frameMC);
25729                 this.frameMC.remove();
25730
25731                 // Restore the reference to the old frame mc as the framebody
25732                 this.frameBody = this.frameMC = oldFrameMC;
25733
25734                 // Apply the new mc classes to the old mc element
25735                 oldFrameMC.dom.className = newMCClassName;
25736
25737                 // Remove the old framing
25738                 if (wasTable) {
25739                     me.el.query('> table')[1].remove();
25740                 }
25741                 else {
25742                     if (oldFrameTL) {
25743                         oldFrameTL.remove();
25744                     }
25745                     if (oldFrameBL) {
25746                         oldFrameBL.remove();
25747                     }
25748                     oldFrameML.remove();
25749                 }
25750             }
25751             else {
25752                 // We were framed but not anymore. Move all content from the old frame to the body
25753
25754             }
25755         }
25756         else if (me.frame) {
25757             this.applyRenderSelectors();
25758         }
25759     },
25760
25761     getFrameInfo: function() {
25762         if (Ext.supports.CSS3BorderRadius) {
25763             return false;
25764         }
25765
25766         var me = this,
25767             left = me.el.getStyle('background-position-x'),
25768             top = me.el.getStyle('background-position-y'),
25769             info, frameInfo = false, max;
25770
25771         // Some browsers dont support background-position-x and y, so for those
25772         // browsers let's split background-position into two parts.
25773         if (!left && !top) {
25774             info = me.el.getStyle('background-position').split(' ');
25775             left = info[0];
25776             top = info[1];
25777         }
25778
25779         // We actually pass a string in the form of '[type][tl][tr]px [type][br][bl]px' as
25780         // the background position of this.el from the css to indicate to IE that this component needs
25781         // framing. We parse it here and change the markup accordingly.
25782         if (parseInt(left, 10) >= 1000000 && parseInt(top, 10) >= 1000000) {
25783             max = Math.max;
25784
25785             frameInfo = {
25786                 // Table markup starts with 110, div markup with 100.
25787                 table: left.substr(0, 3) == '110',
25788
25789                 // Determine if we are dealing with a horizontal or vertical component
25790                 vertical: top.substr(0, 3) == '110',
25791
25792                 // Get and parse the different border radius sizes
25793                 top:    max(left.substr(3, 2), left.substr(5, 2)),
25794                 right:  max(left.substr(5, 2), top.substr(3, 2)),
25795                 bottom: max(top.substr(3, 2), top.substr(5, 2)),
25796                 left:   max(top.substr(5, 2), left.substr(3, 2))
25797             };
25798
25799             frameInfo.width = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left);
25800
25801             // Just to be sure we set the background image of the el to none.
25802             me.el.setStyle('background-image', 'none');
25803         }
25804
25805         // This happens when you set frame: true explicitly without using the x-frame mixin in sass.
25806         // This way IE can't figure out what sizes to use and thus framing can't work.
25807         if (me.frame === true && !frameInfo) {
25808             Ext.Error.raise("You have set frame: true explicity on this component while it doesn't have any " +
25809                             "framing defined in the CSS template. In this case IE can't figure out what sizes " +
25810                             "to use and thus framing on this component will be disabled.");
25811         }
25812
25813         me.frame = me.frame || !!frameInfo;
25814         me.frameSize = frameInfo || false;
25815
25816         return frameInfo;
25817     },
25818
25819     getFramePositions: function(frameInfo) {
25820         var me = this,
25821             frameWidth = frameInfo.width,
25822             dock = me.dock,
25823             positions, tc, bc, ml, mr;
25824
25825         if (frameInfo.vertical) {
25826             tc = '0 -' + (frameWidth * 0) + 'px';
25827             bc = '0 -' + (frameWidth * 1) + 'px';
25828
25829             if (dock && dock == "right") {
25830                 tc = 'right -' + (frameWidth * 0) + 'px';
25831                 bc = 'right -' + (frameWidth * 1) + 'px';
25832             }
25833
25834             positions = {
25835                 tl: '0 -' + (frameWidth * 0) + 'px',
25836                 tr: '0 -' + (frameWidth * 1) + 'px',
25837                 bl: '0 -' + (frameWidth * 2) + 'px',
25838                 br: '0 -' + (frameWidth * 3) + 'px',
25839
25840                 ml: '-' + (frameWidth * 1) + 'px 0',
25841                 mr: 'right 0',
25842
25843                 tc: tc,
25844                 bc: bc
25845             };
25846         } else {
25847             ml = '-' + (frameWidth * 0) + 'px 0';
25848             mr = 'right 0';
25849
25850             if (dock && dock == "bottom") {
25851                 ml = 'left bottom';
25852                 mr = 'right bottom';
25853             }
25854
25855             positions = {
25856                 tl: '0 -' + (frameWidth * 2) + 'px',
25857                 tr: 'right -' + (frameWidth * 3) + 'px',
25858                 bl: '0 -' + (frameWidth * 4) + 'px',
25859                 br: 'right -' + (frameWidth * 5) + 'px',
25860
25861                 ml: ml,
25862                 mr: mr,
25863
25864                 tc: '0 -' + (frameWidth * 0) + 'px',
25865                 bc: '0 -' + (frameWidth * 1) + 'px'
25866             };
25867         }
25868
25869         return positions;
25870     },
25871
25872     /**
25873      * @private
25874      */
25875     getFrameTpl : function(table) {
25876         return table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl');
25877     },
25878
25879     /**
25880      * Creates an array of class names from the configurations to add to this Component's `el` on render.
25881      *
25882      * Private, but (possibly) used by ComponentQuery for selection by class name if Component is not rendered.
25883      *
25884      * @return {String[]} An array of class names with which the Component's element will be rendered.
25885      * @private
25886      */
25887     initCls: function() {
25888         var me = this,
25889             cls = [];
25890
25891         cls.push(me.baseCls);
25892
25893         if (Ext.isDefined(me.cmpCls)) {
25894             if (Ext.isDefined(Ext.global.console)) {
25895                 Ext.global.console.warn('Ext.Component: cmpCls has been deprecated. Please use componentCls.');
25896             }
25897             me.componentCls = me.cmpCls;
25898             delete me.cmpCls;
25899         }
25900
25901         if (me.componentCls) {
25902             cls.push(me.componentCls);
25903         } else {
25904             me.componentCls = me.baseCls;
25905         }
25906         if (me.cls) {
25907             cls.push(me.cls);
25908             delete me.cls;
25909         }
25910
25911         return cls.concat(me.additionalCls);
25912     },
25913
25914     /**
25915      * Sets the UI for the component. This will remove any existing UIs on the component. It will also loop through any
25916      * uiCls set on the component and rename them so they include the new UI
25917      * @param {String} ui The new UI for the component
25918      */
25919     setUI: function(ui) {
25920         var me = this,
25921             oldUICls = Ext.Array.clone(me.uiCls),
25922             newUICls = [],
25923             classes = [],
25924             cls,
25925             i;
25926
25927         //loop through all exisiting uiCls and update the ui in them
25928         for (i = 0; i < oldUICls.length; i++) {
25929             cls = oldUICls[i];
25930
25931             classes = classes.concat(me.removeClsWithUI(cls, true));
25932             newUICls.push(cls);
25933         }
25934
25935         if (classes.length) {
25936             me.removeCls(classes);
25937         }
25938
25939         //remove the UI from the element
25940         me.removeUIFromElement();
25941
25942         //set the UI
25943         me.ui = ui;
25944
25945         //add the new UI to the elemend
25946         me.addUIToElement();
25947
25948         //loop through all exisiting uiCls and update the ui in them
25949         classes = [];
25950         for (i = 0; i < newUICls.length; i++) {
25951             cls = newUICls[i];
25952             classes = classes.concat(me.addClsWithUI(cls, true));
25953         }
25954
25955         if (classes.length) {
25956             me.addCls(classes);
25957         }
25958     },
25959
25960     /**
25961      * Adds a cls to the uiCls array, which will also call {@link #addUIClsToElement} and adds to all elements of this
25962      * component.
25963      * @param {String/String[]} cls A string or an array of strings to add to the uiCls
25964      * @param {Object} skip (Boolean) skip True to skip adding it to the class and do it later (via the return)
25965      */
25966     addClsWithUI: function(cls, skip) {
25967         var me = this,
25968             classes = [],
25969             i;
25970
25971         if (!Ext.isArray(cls)) {
25972             cls = [cls];
25973         }
25974
25975         for (i = 0; i < cls.length; i++) {
25976             if (cls[i] && !me.hasUICls(cls[i])) {
25977                 me.uiCls = Ext.Array.clone(me.uiCls);
25978                 me.uiCls.push(cls[i]);
25979
25980                 classes = classes.concat(me.addUIClsToElement(cls[i]));
25981             }
25982         }
25983
25984         if (skip !== true) {
25985             me.addCls(classes);
25986         }
25987
25988         return classes;
25989     },
25990
25991     /**
25992      * Removes a cls to the uiCls array, which will also call {@link #removeUIClsFromElement} and removes it from all
25993      * elements of this component.
25994      * @param {String/String[]} cls A string or an array of strings to remove to the uiCls
25995      */
25996     removeClsWithUI: function(cls, skip) {
25997         var me = this,
25998             classes = [],
25999             i;
26000
26001         if (!Ext.isArray(cls)) {
26002             cls = [cls];
26003         }
26004
26005         for (i = 0; i < cls.length; i++) {
26006             if (cls[i] && me.hasUICls(cls[i])) {
26007                 me.uiCls = Ext.Array.remove(me.uiCls, cls[i]);
26008
26009                 classes = classes.concat(me.removeUIClsFromElement(cls[i]));
26010             }
26011         }
26012
26013         if (skip !== true) {
26014             me.removeCls(classes);
26015         }
26016
26017         return classes;
26018     },
26019
26020     /**
26021      * Checks if there is currently a specified uiCls
26022      * @param {String} cls The cls to check
26023      */
26024     hasUICls: function(cls) {
26025         var me = this,
26026             uiCls = me.uiCls || [];
26027
26028         return Ext.Array.contains(uiCls, cls);
26029     },
26030
26031     /**
26032      * Method which adds a specified UI + uiCls to the components element. Can be overridden to remove the UI from more
26033      * than just the components element.
26034      * @param {String} ui The UI to remove from the element
26035      */
26036     addUIClsToElement: function(cls, force) {
26037         var me = this,
26038             result = [],
26039             frameElementCls = me.frameElementCls;
26040
26041         result.push(Ext.baseCSSPrefix + cls);
26042         result.push(me.baseCls + '-' + cls);
26043         result.push(me.baseCls + '-' + me.ui + '-' + cls);
26044
26045         if (!force && me.frame && !Ext.supports.CSS3BorderRadius) {
26046             // define each element of the frame
26047             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
26048                 classes, i, j, el;
26049
26050             // loop through each of them, and if they are defined add the ui
26051             for (i = 0; i < els.length; i++) {
26052                 el = me['frame' + els[i].toUpperCase()];
26053                 classes = [me.baseCls + '-' + me.ui + '-' + els[i], me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i]];
26054                 if (el && el.dom) {
26055                     el.addCls(classes);
26056                 } else {
26057                     for (j = 0; j < classes.length; j++) {
26058                         if (Ext.Array.indexOf(frameElementCls[els[i]], classes[j]) == -1) {
26059                             frameElementCls[els[i]].push(classes[j]);
26060                         }
26061                     }
26062                 }
26063             }
26064         }
26065
26066         me.frameElementCls = frameElementCls;
26067
26068         return result;
26069     },
26070
26071     /**
26072      * Method which removes a specified UI + uiCls from the components element. The cls which is added to the element
26073      * will be: `this.baseCls + '-' + ui`
26074      * @param {String} ui The UI to add to the element
26075      */
26076     removeUIClsFromElement: function(cls, force) {
26077         var me = this,
26078             result = [],
26079             frameElementCls = me.frameElementCls;
26080
26081         result.push(Ext.baseCSSPrefix + cls);
26082         result.push(me.baseCls + '-' + cls);
26083         result.push(me.baseCls + '-' + me.ui + '-' + cls);
26084
26085         if (!force && me.frame && !Ext.supports.CSS3BorderRadius) {
26086             // define each element of the frame
26087             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
26088                 i, el;
26089             cls = me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i];
26090             // loop through each of them, and if they are defined add the ui
26091             for (i = 0; i < els.length; i++) {
26092                 el = me['frame' + els[i].toUpperCase()];
26093                 if (el && el.dom) {
26094                     el.removeCls(cls);
26095                 } else {
26096                     Ext.Array.remove(frameElementCls[els[i]], cls);
26097                 }
26098             }
26099         }
26100
26101         me.frameElementCls = frameElementCls;
26102
26103         return result;
26104     },
26105
26106     /**
26107      * Method which adds a specified UI to the components element.
26108      * @private
26109      */
26110     addUIToElement: function(force) {
26111         var me = this,
26112             frameElementCls = me.frameElementCls;
26113
26114         me.addCls(me.baseCls + '-' + me.ui);
26115
26116         if (me.frame && !Ext.supports.CSS3BorderRadius) {
26117             // define each element of the frame
26118             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
26119                 i, el, cls;
26120
26121             // loop through each of them, and if they are defined add the ui
26122             for (i = 0; i < els.length; i++) {
26123                 el = me['frame' + els[i].toUpperCase()];
26124                 cls = me.baseCls + '-' + me.ui + '-' + els[i];
26125                 if (el) {
26126                     el.addCls(cls);
26127                 } else {
26128                     if (!Ext.Array.contains(frameElementCls[els[i]], cls)) {
26129                         frameElementCls[els[i]].push(cls);
26130                     }
26131                 }
26132             }
26133         }
26134     },
26135
26136     /**
26137      * Method which removes a specified UI from the components element.
26138      * @private
26139      */
26140     removeUIFromElement: function() {
26141         var me = this,
26142             frameElementCls = me.frameElementCls;
26143
26144         me.removeCls(me.baseCls + '-' + me.ui);
26145
26146         if (me.frame && !Ext.supports.CSS3BorderRadius) {
26147             // define each element of the frame
26148             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
26149                 i, j, el, cls;
26150
26151             // loop through each of them, and if they are defined add the ui
26152             for (i = 0; i < els.length; i++) {
26153                 el = me['frame' + els[i].toUpperCase()];
26154                 cls = me.baseCls + '-' + me.ui + '-' + els[i];
26155
26156                 if (el) {
26157                     el.removeCls(cls);
26158                 } else {
26159                     Ext.Array.remove(frameElementCls[els[i]], cls);
26160                 }
26161             }
26162         }
26163     },
26164
26165     getElConfig : function() {
26166         if (Ext.isString(this.autoEl)) {
26167             this.autoEl = {
26168                 tag: this.autoEl
26169             };
26170         }
26171
26172         var result = this.autoEl || {tag: 'div'};
26173         result.id = this.id;
26174         return result;
26175     },
26176
26177     /**
26178      * This function takes the position argument passed to onRender and returns a DOM element that you can use in the
26179      * insertBefore.
26180      * @param {String/Number/Ext.Element/HTMLElement} position Index, element id or element you want to put this
26181      * component before.
26182      * @return {HTMLElement} DOM element that you can use in the insertBefore
26183      */
26184     getInsertPosition: function(position) {
26185         // Convert the position to an element to insert before
26186         if (position !== undefined) {
26187             if (Ext.isNumber(position)) {
26188                 position = this.container.dom.childNodes[position];
26189             }
26190             else {
26191                 position = Ext.getDom(position);
26192             }
26193         }
26194
26195         return position;
26196     },
26197
26198     /**
26199      * Adds ctCls to container.
26200      * @return {Ext.Element} The initialized container
26201      * @private
26202      */
26203     initContainer: function(container) {
26204         var me = this;
26205
26206         // If you render a component specifying the el, we get the container
26207         // of the el, and make sure we dont move the el around in the dom
26208         // during the render
26209         if (!container && me.el) {
26210             container = me.el.dom.parentNode;
26211             me.allowDomMove = false;
26212         }
26213
26214         me.container = Ext.get(container);
26215
26216         if (me.ctCls) {
26217             me.container.addCls(me.ctCls);
26218         }
26219
26220         return me.container;
26221     },
26222
26223     /**
26224      * Initialized the renderData to be used when rendering the renderTpl.
26225      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
26226      * @private
26227      */
26228     initRenderData: function() {
26229         var me = this;
26230
26231         return Ext.applyIf(me.renderData, {
26232             id: me.id,
26233             ui: me.ui,
26234             uiCls: me.uiCls,
26235             baseCls: me.baseCls,
26236             componentCls: me.componentCls,
26237             frame: me.frame
26238         });
26239     },
26240
26241     /**
26242      * @private
26243      */
26244     getTpl: function(name) {
26245         var me = this,
26246             prototype = me.self.prototype,
26247             ownerPrototype,
26248             tpl;
26249
26250         if (me.hasOwnProperty(name)) {
26251             tpl = me[name];
26252             if (tpl && !(tpl instanceof Ext.XTemplate)) {
26253                 me[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
26254             }
26255
26256             return me[name];
26257         }
26258
26259         if (!(prototype[name] instanceof Ext.XTemplate)) {
26260             ownerPrototype = prototype;
26261
26262             do {
26263                 if (ownerPrototype.hasOwnProperty(name)) {
26264                     tpl = ownerPrototype[name];
26265                     if (tpl && !(tpl instanceof Ext.XTemplate)) {
26266                         ownerPrototype[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
26267                         break;
26268                     }
26269                 }
26270
26271                 ownerPrototype = ownerPrototype.superclass;
26272             } while (ownerPrototype);
26273         }
26274
26275         return prototype[name];
26276     },
26277
26278     /**
26279      * Initializes the renderTpl.
26280      * @return {Ext.XTemplate} The renderTpl XTemplate instance.
26281      * @private
26282      */
26283     initRenderTpl: function() {
26284         return this.getTpl('renderTpl');
26285     },
26286
26287     /**
26288      * Converts style definitions to String.
26289      * @return {String} A CSS style string with style, padding, margin and border.
26290      * @private
26291      */
26292     initStyles: function() {
26293         var style = {},
26294             me = this,
26295             Element = Ext.Element;
26296
26297         if (Ext.isString(me.style)) {
26298             style = Element.parseStyles(me.style);
26299         } else {
26300             style = Ext.apply({}, me.style);
26301         }
26302
26303         // Convert the padding, margin and border properties from a space seperated string
26304         // into a proper style string
26305         if (me.padding !== undefined) {
26306             style.padding = Element.unitizeBox((me.padding === true) ? 5 : me.padding);
26307         }
26308
26309         if (me.margin !== undefined) {
26310             style.margin = Element.unitizeBox((me.margin === true) ? 5 : me.margin);
26311         }
26312
26313         delete me.style;
26314         return style;
26315     },
26316
26317     /**
26318      * Initializes this components contents. It checks for the properties html, contentEl and tpl/data.
26319      * @private
26320      */
26321     initContent: function() {
26322         var me = this,
26323             target = me.getTargetEl(),
26324             contentEl,
26325             pre;
26326
26327         if (me.html) {
26328             target.update(Ext.DomHelper.markup(me.html));
26329             delete me.html;
26330         }
26331
26332         if (me.contentEl) {
26333             contentEl = Ext.get(me.contentEl);
26334             pre = Ext.baseCSSPrefix;
26335             contentEl.removeCls([pre + 'hidden', pre + 'hide-display', pre + 'hide-offsets', pre + 'hide-nosize']);
26336             target.appendChild(contentEl.dom);
26337         }
26338
26339         if (me.tpl) {
26340             // Make sure this.tpl is an instantiated XTemplate
26341             if (!me.tpl.isTemplate) {
26342                 me.tpl = Ext.create('Ext.XTemplate', me.tpl);
26343             }
26344
26345             if (me.data) {
26346                 me.tpl[me.tplWriteMode](target, me.data);
26347                 delete me.data;
26348             }
26349         }
26350     },
26351
26352     // @private
26353     initEvents : function() {
26354         var me = this,
26355             afterRenderEvents = me.afterRenderEvents,
26356             el,
26357             property,
26358             fn = function(listeners){
26359                 me.mon(el, listeners);
26360             };
26361         if (afterRenderEvents) {
26362             for (property in afterRenderEvents) {
26363                 if (afterRenderEvents.hasOwnProperty(property)) {
26364                     el = me[property];
26365                     if (el && el.on) {
26366                         Ext.each(afterRenderEvents[property], fn);
26367                     }
26368                 }
26369             }
26370         }
26371     },
26372
26373     /**
26374      * Adds each argument passed to this method to the {@link #childEls} array.
26375      */
26376     addChildEls: function () {
26377         var me = this,
26378             childEls = me.childEls || (me.childEls = []);
26379
26380         childEls.push.apply(childEls, arguments);
26381     },
26382
26383     /**
26384      * Removes items in the childEls array based on the return value of a supplied test function. The function is called
26385      * with a entry in childEls and if the test function return true, that entry is removed. If false, that entry is
26386      * kept.
26387      * @param {Function} testFn The test function.
26388      */
26389     removeChildEls: function (testFn) {
26390         var me = this,
26391             old = me.childEls,
26392             keepers = (me.childEls = []),
26393             n, i, cel;
26394
26395         for (i = 0, n = old.length; i < n; ++i) {
26396             cel = old[i];
26397             if (!testFn(cel)) {
26398                 keepers.push(cel);
26399             }
26400         }
26401     },
26402
26403     /**
26404      * Sets references to elements inside the component. This applies {@link #renderSelectors}
26405      * as well as {@link #childEls}.
26406      * @private
26407      */
26408     applyRenderSelectors: function() {
26409         var me = this,
26410             childEls = me.childEls,
26411             selectors = me.renderSelectors,
26412             el = me.el,
26413             dom = el.dom,
26414             baseId, childName, childId, i, selector;
26415
26416         if (childEls) {
26417             baseId = me.id + '-';
26418             for (i = childEls.length; i--; ) {
26419                 childName = childId = childEls[i];
26420                 if (typeof(childName) != 'string') {
26421                     childId = childName.id || (baseId + childName.itemId);
26422                     childName = childName.name;
26423                 } else {
26424                     childId = baseId + childId;
26425                 }
26426
26427                 // We don't use Ext.get because that is 3x (or more) slower on IE6-8. Since
26428                 // we know the el's are children of our el we use getById instead:
26429                 me[childName] = el.getById(childId);
26430             }
26431         }
26432
26433         // We still support renderSelectors. There are a few places in the framework that
26434         // need them and they are a documented part of the API. In fact, we support mixing
26435         // childEls and renderSelectors (no reason not to).
26436         if (selectors) {
26437             for (selector in selectors) {
26438                 if (selectors.hasOwnProperty(selector) && selectors[selector]) {
26439                     me[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], dom));
26440                 }
26441             }
26442         }
26443     },
26444
26445     /**
26446      * Tests whether this Component matches the selector string.
26447      * @param {String} selector The selector string to test against.
26448      * @return {Boolean} True if this Component matches the selector.
26449      */
26450     is: function(selector) {
26451         return Ext.ComponentQuery.is(this, selector);
26452     },
26453
26454     /**
26455      * Walks up the `ownerCt` axis looking for an ancestor Container which matches the passed simple selector.
26456      *
26457      * Example:
26458      *
26459      *     var owningTabPanel = grid.up('tabpanel');
26460      *
26461      * @param {String} [selector] The simple selector to test.
26462      * @return {Ext.container.Container} The matching ancestor Container (or `undefined` if no match was found).
26463      */
26464     up: function(selector) {
26465         var result = this.ownerCt;
26466         if (selector) {
26467             for (; result; result = result.ownerCt) {
26468                 if (Ext.ComponentQuery.is(result, selector)) {
26469                     return result;
26470                 }
26471             }
26472         }
26473         return result;
26474     },
26475
26476     /**
26477      * Returns the next sibling of this Component.
26478      *
26479      * Optionally selects the next sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.
26480      *
26481      * May also be refered to as **`next()`**
26482      *
26483      * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
26484      * {@link #nextNode}
26485      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following items.
26486      * @return {Ext.Component} The next sibling (or the next sibling which matches the selector).
26487      * Returns null if there is no matching sibling.
26488      */
26489     nextSibling: function(selector) {
26490         var o = this.ownerCt, it, last, idx, c;
26491         if (o) {
26492             it = o.items;
26493             idx = it.indexOf(this) + 1;
26494             if (idx) {
26495                 if (selector) {
26496                     for (last = it.getCount(); idx < last; idx++) {
26497                         if ((c = it.getAt(idx)).is(selector)) {
26498                             return c;
26499                         }
26500                     }
26501                 } else {
26502                     if (idx < it.getCount()) {
26503                         return it.getAt(idx);
26504                     }
26505                 }
26506             }
26507         }
26508         return null;
26509     },
26510
26511     /**
26512      * Returns the previous sibling of this Component.
26513      *
26514      * Optionally selects the previous sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery}
26515      * selector.
26516      *
26517      * May also be refered to as **`prev()`**
26518      *
26519      * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
26520      * {@link #previousNode}
26521      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding items.
26522      * @return {Ext.Component} The previous sibling (or the previous sibling which matches the selector).
26523      * Returns null if there is no matching sibling.
26524      */
26525     previousSibling: function(selector) {
26526         var o = this.ownerCt, it, idx, c;
26527         if (o) {
26528             it = o.items;
26529             idx = it.indexOf(this);
26530             if (idx != -1) {
26531                 if (selector) {
26532                     for (--idx; idx >= 0; idx--) {
26533                         if ((c = it.getAt(idx)).is(selector)) {
26534                             return c;
26535                         }
26536                     }
26537                 } else {
26538                     if (idx) {
26539                         return it.getAt(--idx);
26540                     }
26541                 }
26542             }
26543         }
26544         return null;
26545     },
26546
26547     /**
26548      * Returns the previous node in the Component tree in tree traversal order.
26549      *
26550      * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
26551      * tree in reverse order to attempt to find a match. Contrast with {@link #previousSibling}.
26552      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding nodes.
26553      * @return {Ext.Component} The previous node (or the previous node which matches the selector).
26554      * Returns null if there is no matching node.
26555      */
26556     previousNode: function(selector, includeSelf) {
26557         var node = this,
26558             result,
26559             it, len, i;
26560
26561         // If asked to include self, test me
26562         if (includeSelf && node.is(selector)) {
26563             return node;
26564         }
26565
26566         result = this.prev(selector);
26567         if (result) {
26568             return result;
26569         }
26570
26571         if (node.ownerCt) {
26572             for (it = node.ownerCt.items.items, i = Ext.Array.indexOf(it, node) - 1; i > -1; i--) {
26573                 if (it[i].query) {
26574                     result = it[i].query(selector);
26575                     result = result[result.length - 1];
26576                     if (result) {
26577                         return result;
26578                     }
26579                 }
26580             }
26581             return node.ownerCt.previousNode(selector, true);
26582         }
26583     },
26584
26585     /**
26586      * Returns the next node in the Component tree in tree traversal order.
26587      *
26588      * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
26589      * tree to attempt to find a match. Contrast with {@link #nextSibling}.
26590      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following nodes.
26591      * @return {Ext.Component} The next node (or the next node which matches the selector).
26592      * Returns null if there is no matching node.
26593      */
26594     nextNode: function(selector, includeSelf) {
26595         var node = this,
26596             result,
26597             it, len, i;
26598
26599         // If asked to include self, test me
26600         if (includeSelf && node.is(selector)) {
26601             return node;
26602         }
26603
26604         result = this.next(selector);
26605         if (result) {
26606             return result;
26607         }
26608
26609         if (node.ownerCt) {
26610             for (it = node.ownerCt.items, i = it.indexOf(node) + 1, it = it.items, len = it.length; i < len; i++) {
26611                 if (it[i].down) {
26612                     result = it[i].down(selector);
26613                     if (result) {
26614                         return result;
26615                     }
26616                 }
26617             }
26618             return node.ownerCt.nextNode(selector);
26619         }
26620     },
26621
26622     /**
26623      * Retrieves the id of this component. Will autogenerate an id if one has not already been set.
26624      * @return {String}
26625      */
26626     getId : function() {
26627         return this.id || (this.id = 'ext-comp-' + (this.getAutoId()));
26628     },
26629
26630     getItemId : function() {
26631         return this.itemId || this.id;
26632     },
26633
26634     /**
26635      * Retrieves the top level element representing this component.
26636      * @return {Ext.core.Element}
26637      */
26638     getEl : function() {
26639         return this.el;
26640     },
26641
26642     /**
26643      * This is used to determine where to insert the 'html', 'contentEl' and 'items' in this component.
26644      * @private
26645      */
26646     getTargetEl: function() {
26647         return this.frameBody || this.el;
26648     },
26649
26650     /**
26651      * Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
26652      * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).
26653      *
26654      * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
26655      * determination of inherited xtypes.**
26656      *
26657      * For a list of all available xtypes, see the {@link Ext.Component} header.
26658      *
26659      * Example usage:
26660      *
26661      *     var t = new Ext.form.field.Text();
26662      *     var isText = t.isXType('textfield');        // true
26663      *     var isBoxSubclass = t.isXType('field');       // true, descended from Ext.form.field.Base
26664      *     var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.form.field.Base instance
26665      *
26666      * @param {String} xtype The xtype to check for this Component
26667      * @param {Boolean} [shallow=false] True to check whether this Component is directly of the specified xtype, false to
26668      * check whether this Component is descended from the xtype.
26669      * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
26670      */
26671     isXType: function(xtype, shallow) {
26672         //assume a string by default
26673         if (Ext.isFunction(xtype)) {
26674             xtype = xtype.xtype;
26675             //handle being passed the class, e.g. Ext.Component
26676         } else if (Ext.isObject(xtype)) {
26677             xtype = xtype.statics().xtype;
26678             //handle being passed an instance
26679         }
26680
26681         return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1: this.self.xtype == xtype;
26682     },
26683
26684     /**
26685      * Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all available xtypes, see the
26686      * {@link Ext.Component} header.
26687      *
26688      * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
26689      * determination of inherited xtypes.**
26690      *
26691      * Example usage:
26692      *
26693      *     var t = new Ext.form.field.Text();
26694      *     alert(t.getXTypes());  // alerts 'component/field/textfield'
26695      *
26696      * @return {String} The xtype hierarchy string
26697      */
26698     getXTypes: function() {
26699         var self = this.self,
26700             xtypes, parentPrototype, parentXtypes;
26701
26702         if (!self.xtypes) {
26703             xtypes = [];
26704             parentPrototype = this;
26705
26706             while (parentPrototype) {
26707                 parentXtypes = parentPrototype.xtypes;
26708
26709                 if (parentXtypes !== undefined) {
26710                     xtypes.unshift.apply(xtypes, parentXtypes);
26711                 }
26712
26713                 parentPrototype = parentPrototype.superclass;
26714             }
26715
26716             self.xtypeChain = xtypes;
26717             self.xtypes = xtypes.join('/');
26718         }
26719
26720         return self.xtypes;
26721     },
26722
26723     /**
26724      * Update the content area of a component.
26725      * @param {String/Object} htmlOrData If this component has been configured with a template via the tpl config then
26726      * it will use this argument as data to populate the template. If this component was not configured with a template,
26727      * the components content area will be updated via Ext.Element update
26728      * @param {Boolean} [loadScripts=false] Only legitimate when using the html configuration.
26729      * @param {Function} [callback] Only legitimate when using the html configuration. Callback to execute when
26730      * scripts have finished loading
26731      */
26732     update : function(htmlOrData, loadScripts, cb) {
26733         var me = this;
26734
26735         if (me.tpl && !Ext.isString(htmlOrData)) {
26736             me.data = htmlOrData;
26737             if (me.rendered) {
26738                 me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {});
26739             }
26740         } else {
26741             me.html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData;
26742             if (me.rendered) {
26743                 me.getTargetEl().update(me.html, loadScripts, cb);
26744             }
26745         }
26746
26747         if (me.rendered) {
26748             me.doComponentLayout();
26749         }
26750     },
26751
26752     /**
26753      * Convenience function to hide or show this component by boolean.
26754      * @param {Boolean} visible True to show, false to hide
26755      * @return {Ext.Component} this
26756      */
26757     setVisible : function(visible) {
26758         return this[visible ? 'show': 'hide']();
26759     },
26760
26761     /**
26762      * Returns true if this component is visible.
26763      *
26764      * @param {Boolean} [deep=false] Pass `true` to interrogate the visibility status of all parent Containers to
26765      * determine whether this Component is truly visible to the user.
26766      *
26767      * Generally, to determine whether a Component is hidden, the no argument form is needed. For example when creating
26768      * dynamically laid out UIs in a hidden Container before showing them.
26769      *
26770      * @return {Boolean} True if this component is visible, false otherwise.
26771      */
26772     isVisible: function(deep) {
26773         var me = this,
26774             child = me,
26775             visible = !me.hidden,
26776             ancestor = me.ownerCt;
26777
26778         // Clear hiddenOwnerCt property
26779         me.hiddenAncestor = false;
26780         if (me.destroyed) {
26781             return false;
26782         }
26783
26784         if (deep && visible && me.rendered && ancestor) {
26785             while (ancestor) {
26786                 // If any ancestor is hidden, then this is hidden.
26787                 // If an ancestor Panel (only Panels have a collapse method) is collapsed,
26788                 // then its layoutTarget (body) is hidden, so this is hidden unless its within a
26789                 // docked item; they are still visible when collapsed (Unless they themseves are hidden)
26790                 if (ancestor.hidden || (ancestor.collapsed &&
26791                         !(ancestor.getDockedItems && Ext.Array.contains(ancestor.getDockedItems(), child)))) {
26792                     // Store hiddenOwnerCt property if needed
26793                     me.hiddenAncestor = ancestor;
26794                     visible = false;
26795                     break;
26796                 }
26797                 child = ancestor;
26798                 ancestor = ancestor.ownerCt;
26799             }
26800         }
26801         return visible;
26802     },
26803
26804     /**
26805      * Enable the component
26806      * @param {Boolean} [silent=false] Passing true will supress the 'enable' event from being fired.
26807      */
26808     enable: function(silent) {
26809         var me = this;
26810
26811         if (me.rendered) {
26812             me.el.removeCls(me.disabledCls);
26813             me.el.dom.disabled = false;
26814             me.onEnable();
26815         }
26816
26817         me.disabled = false;
26818
26819         if (silent !== true) {
26820             me.fireEvent('enable', me);
26821         }
26822
26823         return me;
26824     },
26825
26826     /**
26827      * Disable the component.
26828      * @param {Boolean} [silent=false] Passing true will supress the 'disable' event from being fired.
26829      */
26830     disable: function(silent) {
26831         var me = this;
26832
26833         if (me.rendered) {
26834             me.el.addCls(me.disabledCls);
26835             me.el.dom.disabled = true;
26836             me.onDisable();
26837         }
26838
26839         me.disabled = true;
26840
26841         if (silent !== true) {
26842             me.fireEvent('disable', me);
26843         }
26844
26845         return me;
26846     },
26847
26848     // @private
26849     onEnable: function() {
26850         if (this.maskOnDisable) {
26851             this.el.unmask();
26852         }
26853     },
26854
26855     // @private
26856     onDisable : function() {
26857         if (this.maskOnDisable) {
26858             this.el.mask();
26859         }
26860     },
26861
26862     /**
26863      * Method to determine whether this Component is currently disabled.
26864      * @return {Boolean} the disabled state of this Component.
26865      */
26866     isDisabled : function() {
26867         return this.disabled;
26868     },
26869
26870     /**
26871      * Enable or disable the component.
26872      * @param {Boolean} disabled True to disable.
26873      */
26874     setDisabled : function(disabled) {
26875         return this[disabled ? 'disable': 'enable']();
26876     },
26877
26878     /**
26879      * Method to determine whether this Component is currently set to hidden.
26880      * @return {Boolean} the hidden state of this Component.
26881      */
26882     isHidden : function() {
26883         return this.hidden;
26884     },
26885
26886     /**
26887      * Adds a CSS class to the top level element representing this component.
26888      * @param {String} cls The CSS class name to add
26889      * @return {Ext.Component} Returns the Component to allow method chaining.
26890      */
26891     addCls : function(className) {
26892         var me = this;
26893         if (!className) {
26894             return me;
26895         }
26896         if (!Ext.isArray(className)){
26897             className = className.replace(me.trimRe, '').split(me.spacesRe);
26898         }
26899         if (me.rendered) {
26900             me.el.addCls(className);
26901         }
26902         else {
26903             me.additionalCls = Ext.Array.unique(me.additionalCls.concat(className));
26904         }
26905         return me;
26906     },
26907
26908     /**
26909      * Adds a CSS class to the top level element representing this component.
26910      * @param {String} cls The CSS class name to add
26911      * @return {Ext.Component} Returns the Component to allow method chaining.
26912      */
26913     addClass : function() {
26914         return this.addCls.apply(this, arguments);
26915     },
26916
26917     /**
26918      * Removes a CSS class from the top level element representing this component.
26919      * @param {Object} className
26920      * @return {Ext.Component} Returns the Component to allow method chaining.
26921      */
26922     removeCls : function(className) {
26923         var me = this;
26924
26925         if (!className) {
26926             return me;
26927         }
26928         if (!Ext.isArray(className)){
26929             className = className.replace(me.trimRe, '').split(me.spacesRe);
26930         }
26931         if (me.rendered) {
26932             me.el.removeCls(className);
26933         }
26934         else if (me.additionalCls.length) {
26935             Ext.each(className, function(cls) {
26936                 Ext.Array.remove(me.additionalCls, cls);
26937             });
26938         }
26939         return me;
26940     },
26941
26942     removeClass : function() {
26943         if (Ext.isDefined(Ext.global.console)) {
26944             Ext.global.console.warn('Ext.Component: removeClass has been deprecated. Please use removeCls.');
26945         }
26946         return this.removeCls.apply(this, arguments);
26947     },
26948
26949     addOverCls: function() {
26950         var me = this;
26951         if (!me.disabled) {
26952             me.el.addCls(me.overCls);
26953         }
26954     },
26955
26956     removeOverCls: function() {
26957         this.el.removeCls(this.overCls);
26958     },
26959
26960     addListener : function(element, listeners, scope, options) {
26961         var me = this,
26962             fn,
26963             option;
26964
26965         if (Ext.isString(element) && (Ext.isObject(listeners) || options && options.element)) {
26966             if (options.element) {
26967                 fn = listeners;
26968
26969                 listeners = {};
26970                 listeners[element] = fn;
26971                 element = options.element;
26972                 if (scope) {
26973                     listeners.scope = scope;
26974                 }
26975
26976                 for (option in options) {
26977                     if (options.hasOwnProperty(option)) {
26978                         if (me.eventOptionsRe.test(option)) {
26979                             listeners[option] = options[option];
26980                         }
26981                     }
26982                 }
26983             }
26984
26985             // At this point we have a variable called element,
26986             // and a listeners object that can be passed to on
26987             if (me[element] && me[element].on) {
26988                 me.mon(me[element], listeners);
26989             } else {
26990                 me.afterRenderEvents = me.afterRenderEvents || {};
26991                 if (!me.afterRenderEvents[element]) {
26992                     me.afterRenderEvents[element] = [];
26993                 }
26994                 me.afterRenderEvents[element].push(listeners);
26995             }
26996         }
26997
26998         return me.mixins.observable.addListener.apply(me, arguments);
26999     },
27000
27001     // inherit docs
27002     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
27003         var me = this,
27004             element = managedListener.options ? managedListener.options.element : null;
27005
27006         if (element) {
27007             element = me[element];
27008             if (element && element.un) {
27009                 if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
27010                     element.un(managedListener.ename, managedListener.fn, managedListener.scope);
27011                     if (!isClear) {
27012                         Ext.Array.remove(me.managedListeners, managedListener);
27013                     }
27014                 }
27015             }
27016         } else {
27017             return me.mixins.observable.removeManagedListenerItem.apply(me, arguments);
27018         }
27019     },
27020
27021     /**
27022      * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
27023      * @return {Ext.container.Container} the Container which owns this Component.
27024      */
27025     getBubbleTarget : function() {
27026         return this.ownerCt;
27027     },
27028
27029     /**
27030      * Method to determine whether this Component is floating.
27031      * @return {Boolean} the floating state of this component.
27032      */
27033     isFloating : function() {
27034         return this.floating;
27035     },
27036
27037     /**
27038      * Method to determine whether this Component is draggable.
27039      * @return {Boolean} the draggable state of this component.
27040      */
27041     isDraggable : function() {
27042         return !!this.draggable;
27043     },
27044
27045     /**
27046      * Method to determine whether this Component is droppable.
27047      * @return {Boolean} the droppable state of this component.
27048      */
27049     isDroppable : function() {
27050         return !!this.droppable;
27051     },
27052
27053     /**
27054      * @private
27055      * Method to manage awareness of when components are added to their
27056      * respective Container, firing an added event.
27057      * References are established at add time rather than at render time.
27058      * @param {Ext.container.Container} container Container which holds the component
27059      * @param {Number} pos Position at which the component was added
27060      */
27061     onAdded : function(container, pos) {
27062         this.ownerCt = container;
27063         this.fireEvent('added', this, container, pos);
27064     },
27065
27066     /**
27067      * @private
27068      * Method to manage awareness of when components are removed from their
27069      * respective Container, firing an removed event. References are properly
27070      * cleaned up after removing a component from its owning container.
27071      */
27072     onRemoved : function() {
27073         var me = this;
27074
27075         me.fireEvent('removed', me, me.ownerCt);
27076         delete me.ownerCt;
27077     },
27078
27079     // @private
27080     beforeDestroy : Ext.emptyFn,
27081     // @private
27082     // @private
27083     onResize : Ext.emptyFn,
27084
27085     /**
27086      * Sets the width and height of this Component. This method fires the {@link #resize} event. This method can accept
27087      * either width and height as separate arguments, or you can pass a size object like `{width:10, height:20}`.
27088      *
27089      * @param {Number/String/Object} width The new width to set. This may be one of:
27090      *
27091      *   - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
27092      *   - A String used to set the CSS width style.
27093      *   - A size object in the format `{width: widthValue, height: heightValue}`.
27094      *   - `undefined` to leave the width unchanged.
27095      *
27096      * @param {Number/String} height The new height to set (not required if a size object is passed as the first arg).
27097      * This may be one of:
27098      *
27099      *   - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
27100      *   - A String used to set the CSS height style. Animation may **not** be used.
27101      *   - `undefined` to leave the height unchanged.
27102      *
27103      * @return {Ext.Component} this
27104      */
27105     setSize : function(width, height) {
27106         var me = this,
27107             layoutCollection;
27108
27109         // support for standard size objects
27110         if (Ext.isObject(width)) {
27111             height = width.height;
27112             width  = width.width;
27113         }
27114
27115         // Constrain within configured maxima
27116         if (Ext.isNumber(width)) {
27117             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
27118         }
27119         if (Ext.isNumber(height)) {
27120             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
27121         }
27122
27123         if (!me.rendered || !me.isVisible()) {
27124             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
27125             if (me.hiddenAncestor) {
27126                 layoutCollection = me.hiddenAncestor.layoutOnShow;
27127                 layoutCollection.remove(me);
27128                 layoutCollection.add(me);
27129             }
27130             me.needsLayout = {
27131                 width: width,
27132                 height: height,
27133                 isSetSize: true
27134             };
27135             if (!me.rendered) {
27136                 me.width  = (width !== undefined) ? width : me.width;
27137                 me.height = (height !== undefined) ? height : me.height;
27138             }
27139             return me;
27140         }
27141         me.doComponentLayout(width, height, true);
27142
27143         return me;
27144     },
27145
27146     isFixedWidth: function() {
27147         var me = this,
27148             layoutManagedWidth = me.layoutManagedWidth;
27149
27150         if (Ext.isDefined(me.width) || layoutManagedWidth == 1) {
27151             return true;
27152         }
27153         if (layoutManagedWidth == 2) {
27154             return false;
27155         }
27156         return (me.ownerCt && me.ownerCt.isFixedWidth());
27157     },
27158
27159     isFixedHeight: function() {
27160         var me = this,
27161             layoutManagedHeight = me.layoutManagedHeight;
27162
27163         if (Ext.isDefined(me.height) || layoutManagedHeight == 1) {
27164             return true;
27165         }
27166         if (layoutManagedHeight == 2) {
27167             return false;
27168         }
27169         return (me.ownerCt && me.ownerCt.isFixedHeight());
27170     },
27171
27172     setCalculatedSize : function(width, height, callingContainer) {
27173         var me = this,
27174             layoutCollection;
27175
27176         // support for standard size objects
27177         if (Ext.isObject(width)) {
27178             callingContainer = width.ownerCt;
27179             height = width.height;
27180             width  = width.width;
27181         }
27182
27183         // Constrain within configured maxima
27184         if (Ext.isNumber(width)) {
27185             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
27186         }
27187         if (Ext.isNumber(height)) {
27188             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
27189         }
27190
27191         if (!me.rendered || !me.isVisible()) {
27192             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
27193             if (me.hiddenAncestor) {
27194                 layoutCollection = me.hiddenAncestor.layoutOnShow;
27195                 layoutCollection.remove(me);
27196                 layoutCollection.add(me);
27197             }
27198             me.needsLayout = {
27199                 width: width,
27200                 height: height,
27201                 isSetSize: false,
27202                 ownerCt: callingContainer
27203             };
27204             return me;
27205         }
27206         me.doComponentLayout(width, height, false, callingContainer);
27207
27208         return me;
27209     },
27210
27211     /**
27212      * This method needs to be called whenever you change something on this component that requires the Component's
27213      * layout to be recalculated.
27214      * @param {Object} width
27215      * @param {Object} height
27216      * @param {Object} isSetSize
27217      * @param {Object} callingContainer
27218      * @return {Ext.container.Container} this
27219      */
27220     doComponentLayout : function(width, height, isSetSize, callingContainer) {
27221         var me = this,
27222             componentLayout = me.getComponentLayout(),
27223             lastComponentSize = componentLayout.lastComponentSize || {
27224                 width: undefined,
27225                 height: undefined
27226             };
27227
27228         // collapsed state is not relevant here, so no testing done.
27229         // Only Panels have a collapse method, and that just sets the width/height such that only
27230         // a single docked Header parallel to the collapseTo side are visible, and the Panel body is hidden.
27231         if (me.rendered && componentLayout) {
27232             // If no width passed, then only insert a value if the Component is NOT ALLOWED to autowidth itself.
27233             if (!Ext.isDefined(width)) {
27234                 if (me.isFixedWidth()) {
27235                     width = Ext.isDefined(me.width) ? me.width : lastComponentSize.width;
27236                 }
27237             }
27238             // If no height passed, then only insert a value if the Component is NOT ALLOWED to autoheight itself.
27239             if (!Ext.isDefined(height)) {
27240                 if (me.isFixedHeight()) {
27241                     height = Ext.isDefined(me.height) ? me.height : lastComponentSize.height;
27242                 }
27243             }
27244
27245             if (isSetSize) {
27246                 me.width = width;
27247                 me.height = height;
27248             }
27249
27250             componentLayout.layout(width, height, isSetSize, callingContainer);
27251         }
27252
27253         return me;
27254     },
27255
27256     /**
27257      * Forces this component to redo its componentLayout.
27258      */
27259     forceComponentLayout: function () {
27260         this.doComponentLayout();
27261     },
27262
27263     // @private
27264     setComponentLayout : function(layout) {
27265         var currentLayout = this.componentLayout;
27266         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
27267             currentLayout.setOwner(null);
27268         }
27269         this.componentLayout = layout;
27270         layout.setOwner(this);
27271     },
27272
27273     getComponentLayout : function() {
27274         var me = this;
27275
27276         if (!me.componentLayout || !me.componentLayout.isLayout) {
27277             me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
27278         }
27279         return me.componentLayout;
27280     },
27281
27282     /**
27283      * Occurs after componentLayout is run.
27284      * @param {Number} adjWidth The box-adjusted width that was set
27285      * @param {Number} adjHeight The box-adjusted height that was set
27286      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
27287      * @param {Ext.Component} callingContainer Container requesting the layout. Only used when isSetSize is false.
27288      */
27289     afterComponentLayout: function(width, height, isSetSize, callingContainer) {
27290         var me = this,
27291             layout = me.componentLayout,
27292             oldSize = me.preLayoutSize;
27293
27294         ++me.componentLayoutCounter;
27295         if (!oldSize || ((width !== oldSize.width) || (height !== oldSize.height))) {
27296             me.fireEvent('resize', me, width, height);
27297         }
27298     },
27299
27300     /**
27301      * Occurs before componentLayout is run. Returning false from this method will prevent the componentLayout from
27302      * being executed.
27303      * @param {Number} adjWidth The box-adjusted width that was set
27304      * @param {Number} adjHeight The box-adjusted height that was set
27305      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
27306      * @param {Ext.Component} callingContainer Container requesting sent the layout. Only used when isSetSize is false.
27307      */
27308     beforeComponentLayout: function(width, height, isSetSize, callingContainer) {
27309         this.preLayoutSize = this.componentLayout.lastComponentSize;
27310         return true;
27311     },
27312
27313     /**
27314      * Sets the left and top of the component. To set the page XY position instead, use
27315      * {@link Ext.Component#setPagePosition setPagePosition}. This method fires the {@link #move} event.
27316      * @param {Number} left The new left
27317      * @param {Number} top The new top
27318      * @return {Ext.Component} this
27319      */
27320     setPosition : function(x, y) {
27321         var me = this;
27322
27323         if (Ext.isObject(x)) {
27324             y = x.y;
27325             x = x.x;
27326         }
27327
27328         if (!me.rendered) {
27329             return me;
27330         }
27331
27332         if (x !== undefined || y !== undefined) {
27333             me.el.setBox(x, y);
27334             me.onPosition(x, y);
27335             me.fireEvent('move', me, x, y);
27336         }
27337         return me;
27338     },
27339
27340     /**
27341      * @private
27342      * Called after the component is moved, this method is empty by default but can be implemented by any
27343      * subclass that needs to perform custom logic after a move occurs.
27344      * @param {Number} x The new x position
27345      * @param {Number} y The new y position
27346      */
27347     onPosition: Ext.emptyFn,
27348
27349     /**
27350      * Sets the width of the component. This method fires the {@link #resize} event.
27351      *
27352      * @param {Number} width The new width to setThis may be one of:
27353      *
27354      *   - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
27355      *   - A String used to set the CSS width style.
27356      *
27357      * @return {Ext.Component} this
27358      */
27359     setWidth : function(width) {
27360         return this.setSize(width);
27361     },
27362
27363     /**
27364      * Sets the height of the component. This method fires the {@link #resize} event.
27365      *
27366      * @param {Number} height The new height to set. This may be one of:
27367      *
27368      *   - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
27369      *   - A String used to set the CSS height style.
27370      *   - _undefined_ to leave the height unchanged.
27371      *
27372      * @return {Ext.Component} this
27373      */
27374     setHeight : function(height) {
27375         return this.setSize(undefined, height);
27376     },
27377
27378     /**
27379      * Gets the current size of the component's underlying element.
27380      * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
27381      */
27382     getSize : function() {
27383         return this.el.getSize();
27384     },
27385
27386     /**
27387      * Gets the current width of the component's underlying element.
27388      * @return {Number}
27389      */
27390     getWidth : function() {
27391         return this.el.getWidth();
27392     },
27393
27394     /**
27395      * Gets the current height of the component's underlying element.
27396      * @return {Number}
27397      */
27398     getHeight : function() {
27399         return this.el.getHeight();
27400     },
27401
27402     /**
27403      * Gets the {@link Ext.ComponentLoader} for this Component.
27404      * @return {Ext.ComponentLoader} The loader instance, null if it doesn't exist.
27405      */
27406     getLoader: function(){
27407         var me = this,
27408             autoLoad = me.autoLoad ? (Ext.isObject(me.autoLoad) ? me.autoLoad : {url: me.autoLoad}) : null,
27409             loader = me.loader || autoLoad;
27410
27411         if (loader) {
27412             if (!loader.isLoader) {
27413                 me.loader = Ext.create('Ext.ComponentLoader', Ext.apply({
27414                     target: me,
27415                     autoLoad: autoLoad
27416                 }, loader));
27417             } else {
27418                 loader.setTarget(me);
27419             }
27420             return me.loader;
27421
27422         }
27423         return null;
27424     },
27425
27426     /**
27427      * This method allows you to show or hide a LoadMask on top of this component.
27428      *
27429      * @param {Boolean/Object/String} load True to show the default LoadMask, a config object that will be passed to the
27430      * LoadMask constructor, or a message String to show. False to hide the current LoadMask.
27431      * @param {Boolean} [targetEl=false] True to mask the targetEl of this Component instead of the `this.el`. For example,
27432      * setting this to true on a Panel will cause only the body to be masked.
27433      * @return {Ext.LoadMask} The LoadMask instance that has just been shown.
27434      */
27435     setLoading : function(load, targetEl) {
27436         var me = this,
27437             config;
27438
27439         if (me.rendered) {
27440             if (load !== false && !me.collapsed) {
27441                 if (Ext.isObject(load)) {
27442                     config = load;
27443                 }
27444                 else if (Ext.isString(load)) {
27445                     config = {msg: load};
27446                 }
27447                 else {
27448                     config = {};
27449                 }
27450                 me.loadMask = me.loadMask || Ext.create('Ext.LoadMask', targetEl ? me.getTargetEl() : me.el, config);
27451                 me.loadMask.show();
27452             } else if (me.loadMask) {
27453                 Ext.destroy(me.loadMask);
27454                 me.loadMask = null;
27455             }
27456         }
27457
27458         return me.loadMask;
27459     },
27460
27461     /**
27462      * Sets the dock position of this component in its parent panel. Note that this only has effect if this item is part
27463      * of the dockedItems collection of a parent that has a DockLayout (note that any Panel has a DockLayout by default)
27464      * @param {Object} dock The dock position.
27465      * @param {Boolean} [layoutParent=false] True to re-layout parent.
27466      * @return {Ext.Component} this
27467      */
27468     setDocked : function(dock, layoutParent) {
27469         var me = this;
27470
27471         me.dock = dock;
27472         if (layoutParent && me.ownerCt && me.rendered) {
27473             me.ownerCt.doComponentLayout();
27474         }
27475         return me;
27476     },
27477
27478     onDestroy : function() {
27479         var me = this;
27480
27481         if (me.monitorResize && Ext.EventManager.resizeEvent) {
27482             Ext.EventManager.resizeEvent.removeListener(me.setSize, me);
27483         }
27484         // Destroying the floatingItems ZIndexManager will also destroy descendant floating Components
27485         Ext.destroy(
27486             me.componentLayout,
27487             me.loadMask,
27488             me.floatingItems
27489         );
27490     },
27491
27492     /**
27493      * Remove any references to elements added via renderSelectors/childEls
27494      * @private
27495      */
27496     cleanElementRefs: function(){
27497         var me = this,
27498             i = 0,
27499             childEls = me.childEls,
27500             selectors = me.renderSelectors,
27501             selector,
27502             name,
27503             len;
27504
27505         if (me.rendered) {
27506             if (childEls) {
27507                 for (len = childEls.length; i < len; ++i) {
27508                     name = childEls[i];
27509                     if (typeof(name) != 'string') {
27510                         name = name.name;
27511                     }
27512                     delete me[name];
27513                 }
27514             }
27515
27516             if (selectors) {
27517                 for (selector in selectors) {
27518                     if (selectors.hasOwnProperty(selector)) {
27519                         delete me[selector];
27520                     }
27521                 }
27522             }
27523         }
27524         delete me.rendered;
27525         delete me.el;
27526         delete me.frameBody;
27527     },
27528
27529     /**
27530      * Destroys the Component.
27531      */
27532     destroy : function() {
27533         var me = this;
27534
27535         if (!me.isDestroyed) {
27536             if (me.fireEvent('beforedestroy', me) !== false) {
27537                 me.destroying = true;
27538                 me.beforeDestroy();
27539
27540                 if (me.floating) {
27541                     delete me.floatParent;
27542                     // A zIndexManager is stamped into a *floating* Component when it is added to a Container.
27543                     // If it has no zIndexManager at render time, it is assigned to the global Ext.WindowManager instance.
27544                     if (me.zIndexManager) {
27545                         me.zIndexManager.unregister(me);
27546                     }
27547                 } else if (me.ownerCt && me.ownerCt.remove) {
27548                     me.ownerCt.remove(me, false);
27549                 }
27550
27551                 me.onDestroy();
27552
27553                 // Attempt to destroy all plugins
27554                 Ext.destroy(me.plugins);
27555
27556                 if (me.rendered) {
27557                     me.el.remove();
27558                 }
27559
27560                 me.fireEvent('destroy', me);
27561                 Ext.ComponentManager.unregister(me);
27562
27563                 me.mixins.state.destroy.call(me);
27564
27565                 me.clearListeners();
27566                 // make sure we clean up the element references after removing all events
27567                 me.cleanElementRefs();
27568                 me.destroying = false;
27569                 me.isDestroyed = true;
27570             }
27571         }
27572     },
27573
27574     /**
27575      * Retrieves a plugin by its pluginId which has been bound to this component.
27576      * @param {Object} pluginId
27577      * @return {Ext.AbstractPlugin} plugin instance.
27578      */
27579     getPlugin: function(pluginId) {
27580         var i = 0,
27581             plugins = this.plugins,
27582             ln = plugins.length;
27583         for (; i < ln; i++) {
27584             if (plugins[i].pluginId === pluginId) {
27585                 return plugins[i];
27586             }
27587         }
27588     },
27589
27590     /**
27591      * Determines whether this component is the descendant of a particular container.
27592      * @param {Ext.Container} container
27593      * @return {Boolean} True if it is.
27594      */
27595     isDescendantOf: function(container) {
27596         return !!this.findParentBy(function(p){
27597             return p === container;
27598         });
27599     }
27600 }, function() {
27601     this.createAlias({
27602         on: 'addListener',
27603         prev: 'previousSibling',
27604         next: 'nextSibling'
27605     });
27606 });
27607
27608 /**
27609  * The AbstractPlugin class is the base class from which user-implemented plugins should inherit.
27610  *
27611  * This class defines the essential API of plugins as used by Components by defining the following methods:
27612  *
27613  *   - `init` : The plugin initialization method which the owning Component calls at Component initialization time.
27614  *
27615  *     The Component passes itself as the sole parameter.
27616  *
27617  *     Subclasses should set up bidirectional links between the plugin and its client Component here.
27618  *
27619  *   - `destroy` : The plugin cleanup method which the owning Component calls at Component destruction time.
27620  *
27621  *     Use this method to break links between the plugin and the Component and to free any allocated resources.
27622  *
27623  *   - `enable` : The base implementation just sets the plugin's `disabled` flag to `false`
27624  *
27625  *   - `disable` : The base implementation just sets the plugin's `disabled` flag to `true`
27626  */
27627 Ext.define('Ext.AbstractPlugin', {
27628     disabled: false,
27629
27630     constructor: function(config) {
27631         if (!config.cmp && Ext.global.console) {
27632             Ext.global.console.warn("Attempted to attach a plugin ");
27633         }
27634         Ext.apply(this, config);
27635     },
27636
27637     getCmp: function() {
27638         return this.cmp;
27639     },
27640
27641     /**
27642      * @method
27643      * The init method is invoked after initComponent method has been run for the client Component.
27644      *
27645      * The supplied implementation is empty. Subclasses should perform plugin initialization, and set up bidirectional
27646      * links between the plugin and its client Component in their own implementation of this method.
27647      * @param {Ext.Component} client The client Component which owns this plugin.
27648      */
27649     init: Ext.emptyFn,
27650
27651     /**
27652      * @method
27653      * The destroy method is invoked by the owning Component at the time the Component is being destroyed.
27654      *
27655      * The supplied implementation is empty. Subclasses should perform plugin cleanup in their own implementation of
27656      * this method.
27657      */
27658     destroy: Ext.emptyFn,
27659
27660     /**
27661      * The base implementation just sets the plugin's `disabled` flag to `false`
27662      *
27663      * Plugin subclasses which need more complex processing may implement an overriding implementation.
27664      */
27665     enable: function() {
27666         this.disabled = false;
27667     },
27668
27669     /**
27670      * The base implementation just sets the plugin's `disabled` flag to `true`
27671      *
27672      * Plugin subclasses which need more complex processing may implement an overriding implementation.
27673      */
27674     disable: function() {
27675         this.disabled = true;
27676     }
27677 });
27678 /**
27679  * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
27680  * to a configured URL, or to a URL specified at request time.
27681  *
27682  * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
27683  * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
27684  * in the request options object, or an {@link #requestcomplete event listener}.
27685  *
27686  * # File Uploads
27687  *
27688  * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
27689  * Instead the form is submitted in the standard manner with the DOM &lt;form&gt; element temporarily modified to have its
27690  * target set to refer to a dynamically generated, hidden &lt;iframe&gt; which is inserted into the document but removed
27691  * after the return data has been gathered.
27692  *
27693  * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
27694  * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
27695  * insert the text unchanged into the document body.
27696  *
27697  * Characters which are significant to an HTML parser must be sent as HTML entities, so encode `<` as `&lt;`, `&` as
27698  * `&amp;` etc.
27699  *
27700  * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
27701  * responseText property in order to conform to the requirements of event handlers and callbacks.
27702  *
27703  * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
27704  * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
27705  * packet content.
27706  *
27707  * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
27708  */
27709 Ext.define('Ext.data.Connection', {
27710     mixins: {
27711         observable: 'Ext.util.Observable'
27712     },
27713
27714     statics: {
27715         requestId: 0
27716     },
27717
27718     url: null,
27719     async: true,
27720     method: null,
27721     username: '',
27722     password: '',
27723
27724     /**
27725      * @cfg {Boolean} disableCaching
27726      * True to add a unique cache-buster param to GET requests.
27727      */
27728     disableCaching: true,
27729
27730     /**
27731      * @cfg {Boolean} withCredentials
27732      * True to set `withCredentials = true` on the XHR object
27733      */
27734     withCredentials: false,
27735
27736     /**
27737      * @cfg {Boolean} cors
27738      * True to enable CORS support on the XHR object. Currently the only effect of this option
27739      * is to use the XDomainRequest object instead of XMLHttpRequest if the browser is IE8 or above.
27740      */
27741     cors: false,
27742
27743     /**
27744      * @cfg {String} disableCachingParam
27745      * Change the parameter which is sent went disabling caching through a cache buster.
27746      */
27747     disableCachingParam: '_dc',
27748
27749     /**
27750      * @cfg {Number} timeout
27751      * The timeout in milliseconds to be used for requests.
27752      */
27753     timeout : 30000,
27754
27755     /**
27756      * @cfg {Object} extraParams
27757      * Any parameters to be appended to the request.
27758      */
27759
27760     useDefaultHeader : true,
27761     defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
27762     useDefaultXhrHeader : true,
27763     defaultXhrHeader : 'XMLHttpRequest',
27764
27765     constructor : function(config) {
27766         config = config || {};
27767         Ext.apply(this, config);
27768
27769         this.addEvents(
27770             /**
27771              * @event beforerequest
27772              * Fires before a network request is made to retrieve a data object.
27773              * @param {Ext.data.Connection} conn This Connection object.
27774              * @param {Object} options The options config object passed to the {@link #request} method.
27775              */
27776             'beforerequest',
27777             /**
27778              * @event requestcomplete
27779              * Fires if the request was successfully completed.
27780              * @param {Ext.data.Connection} conn This Connection object.
27781              * @param {Object} response The XHR object containing the response data.
27782              * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
27783              * @param {Object} options The options config object passed to the {@link #request} method.
27784              */
27785             'requestcomplete',
27786             /**
27787              * @event requestexception
27788              * Fires if an error HTTP status was returned from the server.
27789              * See [HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
27790              * for details of HTTP status codes.
27791              * @param {Ext.data.Connection} conn This Connection object.
27792              * @param {Object} response The XHR object containing the response data.
27793              * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
27794              * @param {Object} options The options config object passed to the {@link #request} method.
27795              */
27796             'requestexception'
27797         );
27798         this.requests = {};
27799         this.mixins.observable.constructor.call(this);
27800     },
27801
27802     /**
27803      * Sends an HTTP request to a remote server.
27804      *
27805      * **Important:** Ajax server requests are asynchronous, and this call will
27806      * return before the response has been received. Process any returned data
27807      * in a callback function.
27808      *
27809      *     Ext.Ajax.request({
27810      *         url: 'ajax_demo/sample.json',
27811      *         success: function(response, opts) {
27812      *             var obj = Ext.decode(response.responseText);
27813      *             console.dir(obj);
27814      *         },
27815      *         failure: function(response, opts) {
27816      *             console.log('server-side failure with status code ' + response.status);
27817      *         }
27818      *     });
27819      *
27820      * To execute a callback function in the correct scope, use the `scope` option.
27821      *
27822      * @param {Object} options An object which may contain the following properties:
27823      *
27824      * (The options object may also contain any other property which might be needed to perform
27825      * postprocessing in a callback because it is passed to callback functions.)
27826      *
27827      * @param {String/Function} options.url The URL to which to send the request, or a function
27828      * to call which returns a URL string. The scope of the function is specified by the `scope` option.
27829      * Defaults to the configured `url`.
27830      *
27831      * @param {Object/String/Function} options.params An object containing properties which are
27832      * used as parameters to the request, a url encoded string or a function to call to get either. The scope
27833      * of the function is specified by the `scope` option.
27834      *
27835      * @param {String} options.method The HTTP method to use
27836      * for the request. Defaults to the configured method, or if no method was configured,
27837      * "GET" if no parameters are being sent, and "POST" if parameters are being sent.  Note that
27838      * the method name is case-sensitive and should be all caps.
27839      *
27840      * @param {Function} options.callback The function to be called upon receipt of the HTTP response.
27841      * The callback is called regardless of success or failure and is passed the following parameters:
27842      * @param {Object} options.callback.options The parameter to the request call.
27843      * @param {Boolean} options.callback.success True if the request succeeded.
27844      * @param {Object} options.callback.response The XMLHttpRequest object containing the response data.
27845      * See [www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/) for details about
27846      * accessing elements of the response.
27847      *
27848      * @param {Function} options.success The function to be called upon success of the request.
27849      * The callback is passed the following parameters:
27850      * @param {Object} options.success.response The XMLHttpRequest object containing the response data.
27851      * @param {Object} options.success.options The parameter to the request call.
27852      *
27853      * @param {Function} options.failure The function to be called upon success of the request.
27854      * The callback is passed the following parameters:
27855      * @param {Object} options.failure.response The XMLHttpRequest object containing the response data.
27856      * @param {Object} options.failure.options The parameter to the request call.
27857      *
27858      * @param {Object} options.scope The scope in which to execute the callbacks: The "this" object for
27859      * the callback function. If the `url`, or `params` options were specified as functions from which to
27860      * draw values, then this also serves as the scope for those function calls. Defaults to the browser
27861      * window.
27862      *
27863      * @param {Number} options.timeout The timeout in milliseconds to be used for this request.
27864      * Defaults to 30 seconds.
27865      *
27866      * @param {Ext.Element/HTMLElement/String} options.form The `<form>` Element or the id of the `<form>`
27867      * to pull parameters from.
27868      *
27869      * @param {Boolean} options.isUpload **Only meaningful when used with the `form` option.**
27870      *
27871      * True if the form object is a file upload (will be set automatically if the form was configured
27872      * with **`enctype`** `"multipart/form-data"`).
27873      *
27874      * File uploads are not performed using normal "Ajax" techniques, that is they are **not**
27875      * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
27876      * DOM `<form>` element temporarily modified to have its [target][] set to refer to a dynamically
27877      * generated, hidden `<iframe>` which is inserted into the document but removed after the return data
27878      * has been gathered.
27879      *
27880      * The server response is parsed by the browser to create the document for the IFRAME. If the
27881      * server is using JSON to send the return object, then the [Content-Type][] header must be set to
27882      * "text/html" in order to tell the browser to insert the text unchanged into the document body.
27883      *
27884      * The response text is retrieved from the document, and a fake XMLHttpRequest object is created
27885      * containing a `responseText` property in order to conform to the requirements of event handlers
27886      * and callbacks.
27887      *
27888      * Be aware that file upload packets are sent with the content type [multipart/form][] and some server
27889      * technologies (notably JEE) may require some custom processing in order to retrieve parameter names
27890      * and parameter values from the packet content.
27891      *
27892      * [target]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target
27893      * [Content-Type]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
27894      * [multipart/form]: http://www.faqs.org/rfcs/rfc2388.html
27895      *
27896      * @param {Object} options.headers Request headers to set for the request.
27897      *
27898      * @param {Object} options.xmlData XML document to use for the post. Note: This will be used instead
27899      * of params for the post data. Any params will be appended to the URL.
27900      *
27901      * @param {Object/String} options.jsonData JSON data to use as the post. Note: This will be used
27902      * instead of params for the post data. Any params will be appended to the URL.
27903      *
27904      * @param {Boolean} options.disableCaching True to add a unique cache-buster param to GET requests.
27905      *
27906      * @param {Boolean} options.withCredentials True to add the withCredentials property to the XHR object
27907      *
27908      * @return {Object} The request object. This may be used to cancel the request.
27909      */
27910     request : function(options) {
27911         options = options || {};
27912         var me = this,
27913             scope = options.scope || window,
27914             username = options.username || me.username,
27915             password = options.password || me.password || '',
27916             async,
27917             requestOptions,
27918             request,
27919             headers,
27920             xhr;
27921
27922         if (me.fireEvent('beforerequest', me, options) !== false) {
27923
27924             requestOptions = me.setOptions(options, scope);
27925
27926             if (this.isFormUpload(options) === true) {
27927                 this.upload(options.form, requestOptions.url, requestOptions.data, options);
27928                 return null;
27929             }
27930
27931             // if autoabort is set, cancel the current transactions
27932             if (options.autoAbort === true || me.autoAbort) {
27933                 me.abort();
27934             }
27935
27936             // create a connection object
27937
27938             if ((options.cors === true || me.cors === true) && Ext.isIe && Ext.ieVersion >= 8) {
27939                 xhr = new XDomainRequest();
27940             } else {
27941                 xhr = this.getXhrInstance();
27942             }
27943
27944             async = options.async !== false ? (options.async || me.async) : false;
27945
27946             // open the request
27947             if (username) {
27948                 xhr.open(requestOptions.method, requestOptions.url, async, username, password);
27949             } else {
27950                 xhr.open(requestOptions.method, requestOptions.url, async);
27951             }
27952
27953             if (options.withCredentials === true || me.withCredentials === true) {
27954                 xhr.withCredentials = true;
27955             }
27956
27957             headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
27958
27959             // create the transaction object
27960             request = {
27961                 id: ++Ext.data.Connection.requestId,
27962                 xhr: xhr,
27963                 headers: headers,
27964                 options: options,
27965                 async: async,
27966                 timeout: setTimeout(function() {
27967                     request.timedout = true;
27968                     me.abort(request);
27969                 }, options.timeout || me.timeout)
27970             };
27971             me.requests[request.id] = request;
27972             me.latestId = request.id;
27973             // bind our statechange listener
27974             if (async) {
27975                 xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
27976             }
27977
27978             // start the request!
27979             xhr.send(requestOptions.data);
27980             if (!async) {
27981                 return this.onComplete(request);
27982             }
27983             return request;
27984         } else {
27985             Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
27986             return null;
27987         }
27988     },
27989
27990     /**
27991      * Uploads a form using a hidden iframe.
27992      * @param {String/HTMLElement/Ext.Element} form The form to upload
27993      * @param {String} url The url to post to
27994      * @param {String} params Any extra parameters to pass
27995      * @param {Object} options The initial options
27996      */
27997     upload: function(form, url, params, options) {
27998         form = Ext.getDom(form);
27999         options = options || {};
28000
28001         var id = Ext.id(),
28002                 frame = document.createElement('iframe'),
28003                 hiddens = [],
28004                 encoding = 'multipart/form-data',
28005                 buf = {
28006                     target: form.target,
28007                     method: form.method,
28008                     encoding: form.encoding,
28009                     enctype: form.enctype,
28010                     action: form.action
28011                 }, hiddenItem;
28012
28013         /*
28014          * Originally this behaviour was modified for Opera 10 to apply the secure URL after
28015          * the frame had been added to the document. It seems this has since been corrected in
28016          * Opera so the behaviour has been reverted, the URL will be set before being added.
28017          */
28018         Ext.fly(frame).set({
28019             id: id,
28020             name: id,
28021             cls: Ext.baseCSSPrefix + 'hide-display',
28022             src: Ext.SSL_SECURE_URL
28023         });
28024
28025         document.body.appendChild(frame);
28026
28027         // This is required so that IE doesn't pop the response up in a new window.
28028         if (document.frames) {
28029            document.frames[id].name = id;
28030         }
28031
28032         Ext.fly(form).set({
28033             target: id,
28034             method: 'POST',
28035             enctype: encoding,
28036             encoding: encoding,
28037             action: url || buf.action
28038         });
28039
28040         // add dynamic params
28041         if (params) {
28042             Ext.iterate(Ext.Object.fromQueryString(params), function(name, value){
28043                 hiddenItem = document.createElement('input');
28044                 Ext.fly(hiddenItem).set({
28045                     type: 'hidden',
28046                     value: value,
28047                     name: name
28048                 });
28049                 form.appendChild(hiddenItem);
28050                 hiddens.push(hiddenItem);
28051             });
28052         }
28053
28054         Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
28055         form.submit();
28056
28057         Ext.fly(form).set(buf);
28058         Ext.each(hiddens, function(h) {
28059             Ext.removeNode(h);
28060         });
28061     },
28062
28063     /**
28064      * @private
28065      * Callback handler for the upload function. After we've submitted the form via the iframe this creates a bogus
28066      * response object to simulate an XHR and populates its responseText from the now-loaded iframe's document body
28067      * (or a textarea inside the body). We then clean up by removing the iframe
28068      */
28069     onUploadComplete: function(frame, options) {
28070         var me = this,
28071             // bogus response object
28072             response = {
28073                 responseText: '',
28074                 responseXML: null
28075             }, doc, firstChild;
28076
28077         try {
28078             doc = frame.contentWindow.document || frame.contentDocument || window.frames[frame.id].document;
28079             if (doc) {
28080                 if (doc.body) {
28081                     if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
28082                         response.responseText = firstChild.value;
28083                     } else {
28084                         response.responseText = doc.body.innerHTML;
28085                     }
28086                 }
28087                 //in IE the document may still have a body even if returns XML.
28088                 response.responseXML = doc.XMLDocument || doc;
28089             }
28090         } catch (e) {
28091         }
28092
28093         me.fireEvent('requestcomplete', me, response, options);
28094
28095         Ext.callback(options.success, options.scope, [response, options]);
28096         Ext.callback(options.callback, options.scope, [options, true, response]);
28097
28098         setTimeout(function(){
28099             Ext.removeNode(frame);
28100         }, 100);
28101     },
28102
28103     /**
28104      * Detects whether the form is intended to be used for an upload.
28105      * @private
28106      */
28107     isFormUpload: function(options){
28108         var form = this.getForm(options);
28109         if (form) {
28110             return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
28111         }
28112         return false;
28113     },
28114
28115     /**
28116      * Gets the form object from options.
28117      * @private
28118      * @param {Object} options The request options
28119      * @return {HTMLElement} The form, null if not passed
28120      */
28121     getForm: function(options){
28122         return Ext.getDom(options.form) || null;
28123     },
28124
28125     /**
28126      * Sets various options such as the url, params for the request
28127      * @param {Object} options The initial options
28128      * @param {Object} scope The scope to execute in
28129      * @return {Object} The params for the request
28130      */
28131     setOptions: function(options, scope){
28132         var me =  this,
28133             params = options.params || {},
28134             extraParams = me.extraParams,
28135             urlParams = options.urlParams,
28136             url = options.url || me.url,
28137             jsonData = options.jsonData,
28138             method,
28139             disableCache,
28140             data;
28141
28142
28143         // allow params to be a method that returns the params object
28144         if (Ext.isFunction(params)) {
28145             params = params.call(scope, options);
28146         }
28147
28148         // allow url to be a method that returns the actual url
28149         if (Ext.isFunction(url)) {
28150             url = url.call(scope, options);
28151         }
28152
28153         url = this.setupUrl(options, url);
28154
28155         if (!url) {
28156             Ext.Error.raise({
28157                 options: options,
28158                 msg: 'No URL specified'
28159             });
28160         }
28161
28162         // check for xml or json data, and make sure json data is encoded
28163         data = options.rawData || options.xmlData || jsonData || null;
28164         if (jsonData && !Ext.isPrimitive(jsonData)) {
28165             data = Ext.encode(data);
28166         }
28167
28168         // make sure params are a url encoded string and include any extraParams if specified
28169         if (Ext.isObject(params)) {
28170             params = Ext.Object.toQueryString(params);
28171         }
28172
28173         if (Ext.isObject(extraParams)) {
28174             extraParams = Ext.Object.toQueryString(extraParams);
28175         }
28176
28177         params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
28178
28179         urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
28180
28181         params = this.setupParams(options, params);
28182
28183         // decide the proper method for this request
28184         method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
28185         this.setupMethod(options, method);
28186
28187
28188         disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
28189         // if the method is get append date to prevent caching
28190         if (method === 'GET' && disableCache) {
28191             url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
28192         }
28193
28194         // if the method is get or there is json/xml data append the params to the url
28195         if ((method == 'GET' || data) && params) {
28196             url = Ext.urlAppend(url, params);
28197             params = null;
28198         }
28199
28200         // allow params to be forced into the url
28201         if (urlParams) {
28202             url = Ext.urlAppend(url, urlParams);
28203         }
28204
28205         return {
28206             url: url,
28207             method: method,
28208             data: data || params || null
28209         };
28210     },
28211
28212     /**
28213      * Template method for overriding url
28214      * @template
28215      * @private
28216      * @param {Object} options
28217      * @param {String} url
28218      * @return {String} The modified url
28219      */
28220     setupUrl: function(options, url){
28221         var form = this.getForm(options);
28222         if (form) {
28223             url = url || form.action;
28224         }
28225         return url;
28226     },
28227
28228
28229     /**
28230      * Template method for overriding params
28231      * @template
28232      * @private
28233      * @param {Object} options
28234      * @param {String} params
28235      * @return {String} The modified params
28236      */
28237     setupParams: function(options, params) {
28238         var form = this.getForm(options),
28239             serializedForm;
28240         if (form && !this.isFormUpload(options)) {
28241             serializedForm = Ext.Element.serializeForm(form);
28242             params = params ? (params + '&' + serializedForm) : serializedForm;
28243         }
28244         return params;
28245     },
28246
28247     /**
28248      * Template method for overriding method
28249      * @template
28250      * @private
28251      * @param {Object} options
28252      * @param {String} method
28253      * @return {String} The modified method
28254      */
28255     setupMethod: function(options, method){
28256         if (this.isFormUpload(options)) {
28257             return 'POST';
28258         }
28259         return method;
28260     },
28261
28262     /**
28263      * Setup all the headers for the request
28264      * @private
28265      * @param {Object} xhr The xhr object
28266      * @param {Object} options The options for the request
28267      * @param {Object} data The data for the request
28268      * @param {Object} params The params for the request
28269      */
28270     setupHeaders: function(xhr, options, data, params){
28271         var me = this,
28272             headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
28273             contentType = me.defaultPostHeader,
28274             jsonData = options.jsonData,
28275             xmlData = options.xmlData,
28276             key,
28277             header;
28278
28279         if (!headers['Content-Type'] && (data || params)) {
28280             if (data) {
28281                 if (options.rawData) {
28282                     contentType = 'text/plain';
28283                 } else {
28284                     if (xmlData && Ext.isDefined(xmlData)) {
28285                         contentType = 'text/xml';
28286                     } else if (jsonData && Ext.isDefined(jsonData)) {
28287                         contentType = 'application/json';
28288                     }
28289                 }
28290             }
28291             headers['Content-Type'] = contentType;
28292         }
28293
28294         if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
28295             headers['X-Requested-With'] = me.defaultXhrHeader;
28296         }
28297         // set up all the request headers on the xhr object
28298         try{
28299             for (key in headers) {
28300                 if (headers.hasOwnProperty(key)) {
28301                     header = headers[key];
28302                     xhr.setRequestHeader(key, header);
28303                 }
28304
28305             }
28306         } catch(e) {
28307             me.fireEvent('exception', key, header);
28308         }
28309         return headers;
28310     },
28311
28312     /**
28313      * Creates the appropriate XHR transport for the browser.
28314      * @private
28315      */
28316     getXhrInstance: (function(){
28317         var options = [function(){
28318             return new XMLHttpRequest();
28319         }, function(){
28320             return new ActiveXObject('MSXML2.XMLHTTP.3.0');
28321         }, function(){
28322             return new ActiveXObject('MSXML2.XMLHTTP');
28323         }, function(){
28324             return new ActiveXObject('Microsoft.XMLHTTP');
28325         }], i = 0,
28326             len = options.length,
28327             xhr;
28328
28329         for(; i < len; ++i) {
28330             try{
28331                 xhr = options[i];
28332                 xhr();
28333                 break;
28334             }catch(e){}
28335         }
28336         return xhr;
28337     })(),
28338
28339     /**
28340      * Determines whether this object has a request outstanding.
28341      * @param {Object} [request] Defaults to the last transaction
28342      * @return {Boolean} True if there is an outstanding request.
28343      */
28344     isLoading : function(request) {
28345         if (!request) {
28346             request = this.getLatest();
28347         }
28348         if (!(request && request.xhr)) {
28349             return false;
28350         }
28351         // if there is a connection and readyState is not 0 or 4
28352         var state = request.xhr.readyState;
28353         return !(state === 0 || state == 4);
28354     },
28355
28356     /**
28357      * Aborts an active request.
28358      * @param {Object} [request] Defaults to the last request
28359      */
28360     abort : function(request) {
28361         var me = this;
28362         
28363         if (!request) {
28364             request = me.getLatest();
28365         }
28366
28367         if (request && me.isLoading(request)) {
28368             /*
28369              * Clear out the onreadystatechange here, this allows us
28370              * greater control, the browser may/may not fire the function
28371              * depending on a series of conditions.
28372              */
28373             request.xhr.onreadystatechange = null;
28374             request.xhr.abort();
28375             me.clearTimeout(request);
28376             if (!request.timedout) {
28377                 request.aborted = true;
28378             }
28379             me.onComplete(request);
28380             me.cleanup(request);
28381         }
28382     },
28383     
28384     /**
28385      * Aborts all active requests
28386      */
28387     abortAll: function(){
28388         var requests = this.requests,
28389             id;
28390         
28391         for (id in requests) {
28392             if (requests.hasOwnProperty(id)) {
28393                 this.abort(requests[id]);
28394             }
28395         }
28396     },
28397     
28398     /**
28399      * Gets the most recent request
28400      * @private
28401      * @return {Object} The request. Null if there is no recent request
28402      */
28403     getLatest: function(){
28404         var id = this.latestId,
28405             request;
28406             
28407         if (id) {
28408             request = this.requests[id];
28409         }
28410         return request || null;
28411     },
28412
28413     /**
28414      * Fires when the state of the xhr changes
28415      * @private
28416      * @param {Object} request The request
28417      */
28418     onStateChange : function(request) {
28419         if (request.xhr.readyState == 4) {
28420             this.clearTimeout(request);
28421             this.onComplete(request);
28422             this.cleanup(request);
28423         }
28424     },
28425
28426     /**
28427      * Clears the timeout on the request
28428      * @private
28429      * @param {Object} The request
28430      */
28431     clearTimeout: function(request){
28432         clearTimeout(request.timeout);
28433         delete request.timeout;
28434     },
28435
28436     /**
28437      * Cleans up any left over information from the request
28438      * @private
28439      * @param {Object} The request
28440      */
28441     cleanup: function(request){
28442         request.xhr = null;
28443         delete request.xhr;
28444     },
28445
28446     /**
28447      * To be called when the request has come back from the server
28448      * @private
28449      * @param {Object} request
28450      * @return {Object} The response
28451      */
28452     onComplete : function(request) {
28453         var me = this,
28454             options = request.options,
28455             result,
28456             success,
28457             response;
28458
28459         try {
28460             result = me.parseStatus(request.xhr.status);
28461         } catch (e) {
28462             // in some browsers we can't access the status if the readyState is not 4, so the request has failed
28463             result = {
28464                 success : false,
28465                 isException : false
28466             };
28467         }
28468         success = result.success;
28469
28470         if (success) {
28471             response = me.createResponse(request);
28472             me.fireEvent('requestcomplete', me, response, options);
28473             Ext.callback(options.success, options.scope, [response, options]);
28474         } else {
28475             if (result.isException || request.aborted || request.timedout) {
28476                 response = me.createException(request);
28477             } else {
28478                 response = me.createResponse(request);
28479             }
28480             me.fireEvent('requestexception', me, response, options);
28481             Ext.callback(options.failure, options.scope, [response, options]);
28482         }
28483         Ext.callback(options.callback, options.scope, [options, success, response]);
28484         delete me.requests[request.id];
28485         return response;
28486     },
28487
28488     /**
28489      * Checks if the response status was successful
28490      * @param {Number} status The status code
28491      * @return {Object} An object containing success/status state
28492      */
28493     parseStatus: function(status) {
28494         // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
28495         status = status == 1223 ? 204 : status;
28496
28497         var success = (status >= 200 && status < 300) || status == 304,
28498             isException = false;
28499
28500         if (!success) {
28501             switch (status) {
28502                 case 12002:
28503                 case 12029:
28504                 case 12030:
28505                 case 12031:
28506                 case 12152:
28507                 case 13030:
28508                     isException = true;
28509                     break;
28510             }
28511         }
28512         return {
28513             success: success,
28514             isException: isException
28515         };
28516     },
28517
28518     /**
28519      * Creates the response object
28520      * @private
28521      * @param {Object} request
28522      */
28523     createResponse : function(request) {
28524         var xhr = request.xhr,
28525             headers = {},
28526             lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
28527             count = lines.length,
28528             line, index, key, value, response;
28529
28530         while (count--) {
28531             line = lines[count];
28532             index = line.indexOf(':');
28533             if(index >= 0) {
28534                 key = line.substr(0, index).toLowerCase();
28535                 if (line.charAt(index + 1) == ' ') {
28536                     ++index;
28537                 }
28538                 headers[key] = line.substr(index + 1);
28539             }
28540         }
28541
28542         request.xhr = null;
28543         delete request.xhr;
28544
28545         response = {
28546             request: request,
28547             requestId : request.id,
28548             status : xhr.status,
28549             statusText : xhr.statusText,
28550             getResponseHeader : function(header){ return headers[header.toLowerCase()]; },
28551             getAllResponseHeaders : function(){ return headers; },
28552             responseText : xhr.responseText,
28553             responseXML : xhr.responseXML
28554         };
28555
28556         // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
28557         // functions created with getResponseHeader/getAllResponseHeaders
28558         xhr = null;
28559         return response;
28560     },
28561
28562     /**
28563      * Creates the exception object
28564      * @private
28565      * @param {Object} request
28566      */
28567     createException : function(request) {
28568         return {
28569             request : request,
28570             requestId : request.id,
28571             status : request.aborted ? -1 : 0,
28572             statusText : request.aborted ? 'transaction aborted' : 'communication failure',
28573             aborted: request.aborted,
28574             timedout: request.timedout
28575         };
28576     }
28577 });
28578
28579 /**
28580  * @class Ext.Ajax
28581  * @singleton
28582  * @markdown
28583  * @extends Ext.data.Connection
28584
28585 A singleton instance of an {@link Ext.data.Connection}. This class
28586 is used to communicate with your server side code. It can be used as follows:
28587
28588     Ext.Ajax.request({
28589         url: 'page.php',
28590         params: {
28591             id: 1
28592         },
28593         success: function(response){
28594             var text = response.responseText;
28595             // process server response here
28596         }
28597     });
28598
28599 Default options for all requests can be set by changing a property on the Ext.Ajax class:
28600
28601     Ext.Ajax.timeout = 60000; // 60 seconds
28602
28603 Any options specified in the request method for the Ajax request will override any
28604 defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
28605 request will be 60 seconds.
28606
28607     Ext.Ajax.timeout = 120000; // 120 seconds
28608     Ext.Ajax.request({
28609         url: 'page.aspx',
28610         timeout: 60000
28611     });
28612
28613 In general, this class will be used for all Ajax requests in your application.
28614 The main reason for creating a separate {@link Ext.data.Connection} is for a
28615 series of requests that share common settings that are different to all other
28616 requests in the application.
28617
28618  */
28619 Ext.define('Ext.Ajax', {
28620     extend: 'Ext.data.Connection',
28621     singleton: true,
28622
28623     /**
28624      * @cfg {String} url @hide
28625      */
28626     /**
28627      * @cfg {Object} extraParams @hide
28628      */
28629     /**
28630      * @cfg {Object} defaultHeaders @hide
28631      */
28632     /**
28633      * @cfg {String} method (Optional) @hide
28634      */
28635     /**
28636      * @cfg {Number} timeout (Optional) @hide
28637      */
28638     /**
28639      * @cfg {Boolean} autoAbort (Optional) @hide
28640      */
28641
28642     /**
28643      * @cfg {Boolean} disableCaching (Optional) @hide
28644      */
28645
28646     /**
28647      * @property {Boolean} disableCaching
28648      * True to add a unique cache-buster param to GET requests. Defaults to true.
28649      */
28650     /**
28651      * @property {String} url
28652      * The default URL to be used for requests to the server.
28653      * If the server receives all requests through one URL, setting this once is easier than
28654      * entering it on every request.
28655      */
28656     /**
28657      * @property {Object} extraParams
28658      * An object containing properties which are used as extra parameters to each request made
28659      * by this object. Session information and other data that you need
28660      * to pass with each request are commonly put here.
28661      */
28662     /**
28663      * @property {Object} defaultHeaders
28664      * An object containing request headers which are added to each request made by this object.
28665      */
28666     /**
28667      * @property {String} method
28668      * The default HTTP method to be used for requests. Note that this is case-sensitive and
28669      * should be all caps (if not set but params are present will use
28670      * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
28671      */
28672     /**
28673      * @property {Number} timeout
28674      * The timeout in milliseconds to be used for requests. Defaults to 30000.
28675      */
28676
28677     /**
28678      * @property {Boolean} autoAbort
28679      * Whether a new request should abort any pending requests.
28680      */
28681     autoAbort : false
28682 });
28683 /**
28684  * A class used to load remote content to an Element. Sample usage:
28685  *
28686  *     Ext.get('el').load({
28687  *         url: 'myPage.php',
28688  *         scripts: true,
28689  *         params: {
28690  *             id: 1
28691  *         }
28692  *     });
28693  *
28694  * In general this class will not be instanced directly, rather the {@link Ext.Element#load} method
28695  * will be used.
28696  */
28697 Ext.define('Ext.ElementLoader', {
28698
28699     /* Begin Definitions */
28700
28701     mixins: {
28702         observable: 'Ext.util.Observable'
28703     },
28704
28705     uses: [
28706         'Ext.data.Connection',
28707         'Ext.Ajax'
28708     ],
28709
28710     statics: {
28711         Renderer: {
28712             Html: function(loader, response, active){
28713                 loader.getTarget().update(response.responseText, active.scripts === true);
28714                 return true;
28715             }
28716         }
28717     },
28718
28719     /* End Definitions */
28720
28721     /**
28722      * @cfg {String} url
28723      * The url to retrieve the content from.
28724      */
28725     url: null,
28726
28727     /**
28728      * @cfg {Object} params
28729      * Any params to be attached to the Ajax request. These parameters will
28730      * be overridden by any params in the load options.
28731      */
28732     params: null,
28733
28734     /**
28735      * @cfg {Object} baseParams Params that will be attached to every request. These parameters
28736      * will not be overridden by any params in the load options.
28737      */
28738     baseParams: null,
28739
28740     /**
28741      * @cfg {Boolean/Object} autoLoad
28742      * True to have the loader make a request as soon as it is created.
28743      * This argument can also be a set of options that will be passed to {@link #load} is called.
28744      */
28745     autoLoad: false,
28746
28747     /**
28748      * @cfg {HTMLElement/Ext.Element/String} target
28749      * The target element for the loader. It can be the DOM element, the id or an {@link Ext.Element}.
28750      */
28751     target: null,
28752
28753     /**
28754      * @cfg {Boolean/String} loadMask
28755      * True or a string to show when the element is loading.
28756      */
28757     loadMask: false,
28758
28759     /**
28760      * @cfg {Object} ajaxOptions
28761      * Any additional options to be passed to the request, for example timeout or headers.
28762      */
28763     ajaxOptions: null,
28764
28765     /**
28766      * @cfg {Boolean} scripts
28767      * True to parse any inline script tags in the response.
28768      */
28769     scripts: false,
28770
28771     /**
28772      * @cfg {Function} success
28773      * A function to be called when a load request is successful.
28774      * Will be called with the following config parameters:
28775      *
28776      * - this - The ElementLoader instance.
28777      * - response - The response object.
28778      * - options - Ajax options.
28779      */
28780
28781     /**
28782      * @cfg {Function} failure A function to be called when a load request fails.
28783      * Will be called with the following config parameters:
28784      *
28785      * - this - The ElementLoader instance.
28786      * - response - The response object.
28787      * - options - Ajax options.
28788      */
28789
28790     /**
28791      * @cfg {Function} callback A function to be called when a load request finishes.
28792      * Will be called with the following config parameters:
28793      *
28794      * - this - The ElementLoader instance.
28795      * - success - True if successful request.
28796      * - response - The response object.
28797      * - options - Ajax options.
28798      */
28799
28800     /**
28801      * @cfg {Object} scope
28802      * The scope to execute the {@link #success} and {@link #failure} functions in.
28803      */
28804
28805     /**
28806      * @cfg {Function} renderer
28807      * A custom function to render the content to the element. The passed parameters are:
28808      *
28809      * - The loader
28810      * - The response
28811      * - The active request
28812      */
28813
28814     isLoader: true,
28815
28816     constructor: function(config) {
28817         var me = this,
28818             autoLoad;
28819
28820         config = config || {};
28821         Ext.apply(me, config);
28822         me.setTarget(me.target);
28823         me.addEvents(
28824             /**
28825              * @event beforeload
28826              * Fires before a load request is made to the server.
28827              * Returning false from an event listener can prevent the load
28828              * from occurring.
28829              * @param {Ext.ElementLoader} this
28830              * @param {Object} options The options passed to the request
28831              */
28832             'beforeload',
28833
28834             /**
28835              * @event exception
28836              * Fires after an unsuccessful load.
28837              * @param {Ext.ElementLoader} this
28838              * @param {Object} response The response from the server
28839              * @param {Object} options The options passed to the request
28840              */
28841             'exception',
28842
28843             /**
28844              * @event load
28845              * Fires after a successful load.
28846              * @param {Ext.ElementLoader} this
28847              * @param {Object} response The response from the server
28848              * @param {Object} options The options passed to the request
28849              */
28850             'load'
28851         );
28852
28853         // don't pass config because we have already applied it.
28854         me.mixins.observable.constructor.call(me);
28855
28856         if (me.autoLoad) {
28857             autoLoad = me.autoLoad;
28858             if (autoLoad === true) {
28859                 autoLoad = {};
28860             }
28861             me.load(autoLoad);
28862         }
28863     },
28864
28865     /**
28866      * Sets an {@link Ext.Element} as the target of this loader.
28867      * Note that if the target is changed, any active requests will be aborted.
28868      * @param {String/HTMLElement/Ext.Element} target The element or its ID.
28869      */
28870     setTarget: function(target){
28871         var me = this;
28872         target = Ext.get(target);
28873         if (me.target && me.target != target) {
28874             me.abort();
28875         }
28876         me.target = target;
28877     },
28878
28879     /**
28880      * Returns the target of this loader.
28881      * @return {Ext.Component} The target or null if none exists.
28882      */
28883     getTarget: function(){
28884         return this.target || null;
28885     },
28886
28887     /**
28888      * Aborts the active load request
28889      */
28890     abort: function(){
28891         var active = this.active;
28892         if (active !== undefined) {
28893             Ext.Ajax.abort(active.request);
28894             if (active.mask) {
28895                 this.removeMask();
28896             }
28897             delete this.active;
28898         }
28899     },
28900
28901     /**
28902      * Removes the mask on the target
28903      * @private
28904      */
28905     removeMask: function(){
28906         this.target.unmask();
28907     },
28908
28909     /**
28910      * Adds the mask on the target
28911      * @private
28912      * @param {Boolean/Object} mask The mask configuration
28913      */
28914     addMask: function(mask){
28915         this.target.mask(mask === true ? null : mask);
28916     },
28917
28918     /**
28919      * Loads new data from the server.
28920      * @param {Object} options The options for the request. They can be any configuration option that can be specified for
28921      * the class, with the exception of the target option. Note that any options passed to the method will override any
28922      * class defaults.
28923      */
28924     load: function(options) {
28925         if (!this.target) {
28926             Ext.Error.raise('A valid target is required when loading content');
28927         }
28928
28929         options = Ext.apply({}, options);
28930
28931         var me = this,
28932             target = me.target,
28933             mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
28934             params = Ext.apply({}, options.params),
28935             ajaxOptions = Ext.apply({}, options.ajaxOptions),
28936             callback = options.callback || me.callback,
28937             scope = options.scope || me.scope || me,
28938             request;
28939
28940         Ext.applyIf(ajaxOptions, me.ajaxOptions);
28941         Ext.applyIf(options, ajaxOptions);
28942
28943         Ext.applyIf(params, me.params);
28944         Ext.apply(params, me.baseParams);
28945
28946         Ext.applyIf(options, {
28947             url: me.url
28948         });
28949
28950         if (!options.url) {
28951             Ext.Error.raise('You must specify the URL from which content should be loaded');
28952         }
28953
28954         Ext.apply(options, {
28955             scope: me,
28956             params: params,
28957             callback: me.onComplete
28958         });
28959
28960         if (me.fireEvent('beforeload', me, options) === false) {
28961             return;
28962         }
28963
28964         if (mask) {
28965             me.addMask(mask);
28966         }
28967
28968         request = Ext.Ajax.request(options);
28969         me.active = {
28970             request: request,
28971             options: options,
28972             mask: mask,
28973             scope: scope,
28974             callback: callback,
28975             success: options.success || me.success,
28976             failure: options.failure || me.failure,
28977             renderer: options.renderer || me.renderer,
28978             scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
28979         };
28980         me.setOptions(me.active, options);
28981     },
28982
28983     /**
28984      * Sets any additional options on the active request
28985      * @private
28986      * @param {Object} active The active request
28987      * @param {Object} options The initial options
28988      */
28989     setOptions: Ext.emptyFn,
28990
28991     /**
28992      * Parses the response after the request completes
28993      * @private
28994      * @param {Object} options Ajax options
28995      * @param {Boolean} success Success status of the request
28996      * @param {Object} response The response object
28997      */
28998     onComplete: function(options, success, response) {
28999         var me = this,
29000             active = me.active,
29001             scope = active.scope,
29002             renderer = me.getRenderer(active.renderer);
29003
29004
29005         if (success) {
29006             success = renderer.call(me, me, response, active);
29007         }
29008
29009         if (success) {
29010             Ext.callback(active.success, scope, [me, response, options]);
29011             me.fireEvent('load', me, response, options);
29012         } else {
29013             Ext.callback(active.failure, scope, [me, response, options]);
29014             me.fireEvent('exception', me, response, options);
29015         }
29016         Ext.callback(active.callback, scope, [me, success, response, options]);
29017
29018         if (active.mask) {
29019             me.removeMask();
29020         }
29021
29022         delete me.active;
29023     },
29024
29025     /**
29026      * Gets the renderer to use
29027      * @private
29028      * @param {String/Function} renderer The renderer to use
29029      * @return {Function} A rendering function to use.
29030      */
29031     getRenderer: function(renderer){
29032         if (Ext.isFunction(renderer)) {
29033             return renderer;
29034         }
29035         return this.statics().Renderer.Html;
29036     },
29037
29038     /**
29039      * Automatically refreshes the content over a specified period.
29040      * @param {Number} interval The interval to refresh in ms.
29041      * @param {Object} options (optional) The options to pass to the load method. See {@link #load}
29042      */
29043     startAutoRefresh: function(interval, options){
29044         var me = this;
29045         me.stopAutoRefresh();
29046         me.autoRefresh = setInterval(function(){
29047             me.load(options);
29048         }, interval);
29049     },
29050
29051     /**
29052      * Clears any auto refresh. See {@link #startAutoRefresh}.
29053      */
29054     stopAutoRefresh: function(){
29055         clearInterval(this.autoRefresh);
29056         delete this.autoRefresh;
29057     },
29058
29059     /**
29060      * Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
29061      * @return {Boolean} True if the loader is automatically refreshing
29062      */
29063     isAutoRefreshing: function(){
29064         return Ext.isDefined(this.autoRefresh);
29065     },
29066
29067     /**
29068      * Destroys the loader. Any active requests will be aborted.
29069      */
29070     destroy: function(){
29071         var me = this;
29072         me.stopAutoRefresh();
29073         delete me.target;
29074         me.abort();
29075         me.clearListeners();
29076     }
29077 });
29078
29079 /**
29080  * @class Ext.ComponentLoader
29081  * @extends Ext.ElementLoader
29082  *
29083  * This class is used to load content via Ajax into a {@link Ext.Component}. In general
29084  * this class will not be instanced directly, rather a loader configuration will be passed to the
29085  * constructor of the {@link Ext.Component}.
29086  *
29087  * ## HTML Renderer
29088  * By default, the content loaded will be processed as raw html. The response text
29089  * from the request is taken and added to the component. This can be used in
29090  * conjunction with the {@link #scripts} option to execute any inline scripts in
29091  * the resulting content. Using this renderer has the same effect as passing the
29092  * {@link Ext.Component#html} configuration option.
29093  *
29094  * ## Data Renderer
29095  * This renderer allows content to be added by using JSON data and a {@link Ext.XTemplate}.
29096  * The content received from the response is passed to the {@link Ext.Component#update} method.
29097  * This content is run through the attached {@link Ext.Component#tpl} and the data is added to
29098  * the Component. Using this renderer has the same effect as using the {@link Ext.Component#data}
29099  * configuration in conjunction with a {@link Ext.Component#tpl}.
29100  *
29101  * ## Component Renderer
29102  * This renderer can only be used with a {@link Ext.container.Container} and subclasses. It allows for
29103  * Components to be loaded remotely into a Container. The response is expected to be a single/series of
29104  * {@link Ext.Component} configuration objects. When the response is received, the data is decoded
29105  * and then passed to {@link Ext.container.Container#add}. Using this renderer has the same effect as specifying
29106  * the {@link Ext.container.Container#items} configuration on a Container.
29107  *
29108  * ## Custom Renderer
29109  * A custom function can be passed to handle any other special case, see the {@link #renderer} option.
29110  *
29111  * ## Example Usage
29112  *     new Ext.Component({
29113  *         tpl: '{firstName} - {lastName}',
29114  *         loader: {
29115  *             url: 'myPage.php',
29116  *             renderer: 'data',
29117  *             params: {
29118  *                 userId: 1
29119  *             }
29120  *         }
29121  *     });
29122  */
29123 Ext.define('Ext.ComponentLoader', {
29124
29125     /* Begin Definitions */
29126
29127     extend: 'Ext.ElementLoader',
29128
29129     statics: {
29130         Renderer: {
29131             Data: function(loader, response, active){
29132                 var success = true;
29133                 try {
29134                     loader.getTarget().update(Ext.decode(response.responseText));
29135                 } catch (e) {
29136                     success = false;
29137                 }
29138                 return success;
29139             },
29140
29141             Component: function(loader, response, active){
29142                 var success = true,
29143                     target = loader.getTarget(),
29144                     items = [];
29145
29146                 if (!target.isContainer) {
29147                     Ext.Error.raise({
29148                         target: target,
29149                         msg: 'Components can only be loaded into a container'
29150                     });
29151                 }
29152
29153                 try {
29154                     items = Ext.decode(response.responseText);
29155                 } catch (e) {
29156                     success = false;
29157                 }
29158
29159                 if (success) {
29160                     if (active.removeAll) {
29161                         target.removeAll();
29162                     }
29163                     target.add(items);
29164                 }
29165                 return success;
29166             }
29167         }
29168     },
29169
29170     /* End Definitions */
29171
29172     /**
29173      * @cfg {Ext.Component/String} target The target {@link Ext.Component} for the loader.
29174      * If a string is passed it will be looked up via the id.
29175      */
29176     target: null,
29177
29178     /**
29179      * @cfg {Boolean/Object} loadMask True or a {@link Ext.LoadMask} configuration to enable masking during loading.
29180      */
29181     loadMask: false,
29182
29183     /**
29184      * @cfg {Boolean} scripts True to parse any inline script tags in the response. This only used when using the html
29185      * {@link #renderer}.
29186      */
29187
29188     /**
29189      * @cfg {String/Function} renderer
29190
29191 The type of content that is to be loaded into, which can be one of 3 types:
29192
29193 + **html** : Loads raw html content, see {@link Ext.Component#html}
29194 + **data** : Loads raw html content, see {@link Ext.Component#data}
29195 + **component** : Loads child {Ext.Component} instances. This option is only valid when used with a Container.
29196
29197 Alternatively, you can pass a function which is called with the following parameters.
29198
29199 + loader - Loader instance
29200 + response - The server response
29201 + active - The active request
29202
29203 The function must return false is loading is not successful. Below is a sample of using a custom renderer:
29204
29205     new Ext.Component({
29206         loader: {
29207             url: 'myPage.php',
29208             renderer: function(loader, response, active) {
29209                 var text = response.responseText;
29210                 loader.getTarget().update('The response is ' + text);
29211                 return true;
29212             }
29213         }
29214     });
29215      */
29216     renderer: 'html',
29217
29218     /**
29219      * Set a {Ext.Component} as the target of this loader. Note that if the target is changed,
29220      * any active requests will be aborted.
29221      * @param {String/Ext.Component} target The component to be the target of this loader. If a string is passed
29222      * it will be looked up via its id.
29223      */
29224     setTarget: function(target){
29225         var me = this;
29226
29227         if (Ext.isString(target)) {
29228             target = Ext.getCmp(target);
29229         }
29230
29231         if (me.target && me.target != target) {
29232             me.abort();
29233         }
29234         me.target = target;
29235     },
29236
29237     // inherit docs
29238     removeMask: function(){
29239         this.target.setLoading(false);
29240     },
29241
29242     /**
29243      * Add the mask on the target
29244      * @private
29245      * @param {Boolean/Object} mask The mask configuration
29246      */
29247     addMask: function(mask){
29248         this.target.setLoading(mask);
29249     },
29250
29251     /**
29252      * Get the target of this loader.
29253      * @return {Ext.Component} target The target, null if none exists.
29254      */
29255
29256     setOptions: function(active, options){
29257         active.removeAll = Ext.isDefined(options.removeAll) ? options.removeAll : this.removeAll;
29258     },
29259
29260     /**
29261      * Gets the renderer to use
29262      * @private
29263      * @param {String/Function} renderer The renderer to use
29264      * @return {Function} A rendering function to use.
29265      */
29266     getRenderer: function(renderer){
29267         if (Ext.isFunction(renderer)) {
29268             return renderer;
29269         }
29270
29271         var renderers = this.statics().Renderer;
29272         switch (renderer) {
29273             case 'component':
29274                 return renderers.Component;
29275             case 'data':
29276                 return renderers.Data;
29277             default:
29278                 return Ext.ElementLoader.Renderer.Html;
29279         }
29280     }
29281 });
29282
29283 /**
29284  * @author Ed Spencer
29285  *
29286  * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
29287  * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
29288  * express like this:
29289  *
29290  *     Ext.define('User', {
29291  *         extend: 'Ext.data.Model',
29292  *         fields: ['id', 'name', 'email'],
29293  *
29294  *         hasMany: {model: 'Order', name: 'orders'}
29295  *     });
29296  *
29297  *     Ext.define('Order', {
29298  *         extend: 'Ext.data.Model',
29299  *         fields: ['id', 'user_id', 'status', 'price'],
29300  *
29301  *         belongsTo: 'User'
29302  *     });
29303  *
29304  * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
29305  * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and {@link
29306  * Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
29307  * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
29308  *
29309  * **Further Reading**
29310  *
29311  *   - {@link Ext.data.HasManyAssociation hasMany associations}
29312  *   - {@link Ext.data.BelongsToAssociation belongsTo associations}
29313  *   - {@link Ext.data.Model using Models}
29314  *
29315  * # Self association models
29316  *
29317  * We can also have models that create parent/child associations between the same type. Below is an example, where
29318  * groups can be nested inside other groups:
29319  *
29320  *     // Server Data
29321  *     {
29322  *         "groups": {
29323  *             "id": 10,
29324  *             "parent_id": 100,
29325  *             "name": "Main Group",
29326  *             "parent_group": {
29327  *                 "id": 100,
29328  *                 "parent_id": null,
29329  *                 "name": "Parent Group"
29330  *             },
29331  *             "child_groups": [{
29332  *                 "id": 2,
29333  *                 "parent_id": 10,
29334  *                 "name": "Child Group 1"
29335  *             },{
29336  *                 "id": 3,
29337  *                 "parent_id": 10,
29338  *                 "name": "Child Group 2"
29339  *             },{
29340  *                 "id": 4,
29341  *                 "parent_id": 10,
29342  *                 "name": "Child Group 3"
29343  *             }]
29344  *         }
29345  *     }
29346  *
29347  *     // Client code
29348  *     Ext.define('Group', {
29349  *         extend: 'Ext.data.Model',
29350  *         fields: ['id', 'parent_id', 'name'],
29351  *         proxy: {
29352  *             type: 'ajax',
29353  *             url: 'data.json',
29354  *             reader: {
29355  *                 type: 'json',
29356  *                 root: 'groups'
29357  *             }
29358  *         },
29359  *         associations: [{
29360  *             type: 'hasMany',
29361  *             model: 'Group',
29362  *             primaryKey: 'id',
29363  *             foreignKey: 'parent_id',
29364  *             autoLoad: true,
29365  *             associationKey: 'child_groups' // read child data from child_groups
29366  *         }, {
29367  *             type: 'belongsTo',
29368  *             model: 'Group',
29369  *             primaryKey: 'id',
29370  *             foreignKey: 'parent_id',
29371  *             associationKey: 'parent_group' // read parent data from parent_group
29372  *         }]
29373  *     });
29374  *
29375  *     Ext.onReady(function(){
29376  *
29377  *         Group.load(10, {
29378  *             success: function(group){
29379  *                 console.log(group.getGroup().get('name'));
29380  *
29381  *                 group.groups().each(function(rec){
29382  *                     console.log(rec.get('name'));
29383  *                 });
29384  *             }
29385  *         });
29386  *
29387  *     });
29388  *
29389  */
29390 Ext.define('Ext.data.Association', {
29391     /**
29392      * @cfg {String} ownerModel (required)
29393      * The string name of the model that owns the association.
29394      */
29395
29396     /**
29397      * @cfg {String} associatedModel (required)
29398      * The string name of the model that is being associated with.
29399      */
29400
29401     /**
29402      * @cfg {String} primaryKey
29403      * The name of the primary key on the associated model. In general this will be the
29404      * {@link Ext.data.Model#idProperty} of the Model.
29405      */
29406     primaryKey: 'id',
29407
29408     /**
29409      * @cfg {Ext.data.reader.Reader} reader
29410      * A special reader to read associated data
29411      */
29412     
29413     /**
29414      * @cfg {String} associationKey
29415      * The name of the property in the data to read the association from. Defaults to the name of the associated model.
29416      */
29417
29418     defaultReaderType: 'json',
29419
29420     statics: {
29421         create: function(association){
29422             if (!association.isAssociation) {
29423                 if (Ext.isString(association)) {
29424                     association = {
29425                         type: association
29426                     };
29427                 }
29428
29429                 switch (association.type) {
29430                     case 'belongsTo':
29431                         return Ext.create('Ext.data.BelongsToAssociation', association);
29432                     case 'hasMany':
29433                         return Ext.create('Ext.data.HasManyAssociation', association);
29434                     //TODO Add this back when it's fixed
29435 //                    case 'polymorphic':
29436 //                        return Ext.create('Ext.data.PolymorphicAssociation', association);
29437                     default:
29438                         Ext.Error.raise('Unknown Association type: "' + association.type + '"');
29439                 }
29440             }
29441             return association;
29442         }
29443     },
29444
29445     /**
29446      * Creates the Association object.
29447      * @param {Object} [config] Config object.
29448      */
29449     constructor: function(config) {
29450         Ext.apply(this, config);
29451
29452         var types           = Ext.ModelManager.types,
29453             ownerName       = config.ownerModel,
29454             associatedName  = config.associatedModel,
29455             ownerModel      = types[ownerName],
29456             associatedModel = types[associatedName],
29457             ownerProto;
29458
29459         if (ownerModel === undefined) {
29460             Ext.Error.raise("The configured ownerModel was not valid (you tried " + ownerName + ")");
29461         }
29462         if (associatedModel === undefined) {
29463             Ext.Error.raise("The configured associatedModel was not valid (you tried " + associatedName + ")");
29464         }
29465
29466         this.ownerModel = ownerModel;
29467         this.associatedModel = associatedModel;
29468
29469         /**
29470          * @property {String} ownerName
29471          * The name of the model that 'owns' the association
29472          */
29473
29474         /**
29475          * @property {String} associatedName
29476          * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is
29477          * 'Order')
29478          */
29479
29480         Ext.applyIf(this, {
29481             ownerName : ownerName,
29482             associatedName: associatedName
29483         });
29484     },
29485
29486     /**
29487      * Get a specialized reader for reading associated data
29488      * @return {Ext.data.reader.Reader} The reader, null if not supplied
29489      */
29490     getReader: function(){
29491         var me = this,
29492             reader = me.reader,
29493             model = me.associatedModel;
29494
29495         if (reader) {
29496             if (Ext.isString(reader)) {
29497                 reader = {
29498                     type: reader
29499                 };
29500             }
29501             if (reader.isReader) {
29502                 reader.setModel(model);
29503             } else {
29504                 Ext.applyIf(reader, {
29505                     model: model,
29506                     type : me.defaultReaderType
29507                 });
29508             }
29509             me.reader = Ext.createByAlias('reader.' + reader.type, reader);
29510         }
29511         return me.reader || null;
29512     }
29513 });
29514
29515 /**
29516  * @author Ed Spencer
29517  * @class Ext.ModelManager
29518  * @extends Ext.AbstractManager
29519
29520 The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
29521
29522 __Creating Model Instances__
29523
29524 Model instances can be created by using the {@link Ext#create Ext.create} method. Ext.create replaces
29525 the deprecated {@link #create Ext.ModelManager.create} method. It is also possible to create a model instance
29526 this by using the Model type directly. The following 3 snippets are equivalent:
29527
29528     Ext.define('User', {
29529         extend: 'Ext.data.Model',
29530         fields: ['first', 'last']
29531     });
29532
29533     // method 1, create using Ext.create (recommended)
29534     Ext.create('User', {
29535         first: 'Ed',
29536         last: 'Spencer'
29537     });
29538
29539     // method 2, create through the manager (deprecated)
29540     Ext.ModelManager.create({
29541         first: 'Ed',
29542         last: 'Spencer'
29543     }, 'User');
29544
29545     // method 3, create on the type directly
29546     new User({
29547         first: 'Ed',
29548         last: 'Spencer'
29549     });
29550
29551 __Accessing Model Types__
29552
29553 A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
29554 are normal classes, you can access the type directly. The following snippets are equivalent:
29555
29556     Ext.define('User', {
29557         extend: 'Ext.data.Model',
29558         fields: ['first', 'last']
29559     });
29560
29561     // method 1, access model type through the manager
29562     var UserType = Ext.ModelManager.getModel('User');
29563
29564     // method 2, reference the type directly
29565     var UserType = User;
29566
29567  * @markdown
29568  * @singleton
29569  */
29570 Ext.define('Ext.ModelManager', {
29571     extend: 'Ext.AbstractManager',
29572     alternateClassName: 'Ext.ModelMgr',
29573     requires: ['Ext.data.Association'],
29574
29575     singleton: true,
29576
29577     typeName: 'mtype',
29578
29579     /**
29580      * Private stack of associations that must be created once their associated model has been defined
29581      * @property {Ext.data.Association[]} associationStack
29582      */
29583     associationStack: [],
29584
29585     /**
29586      * Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
29587      * immediately, as are any addition plugins defined in the model config.
29588      * @private
29589      */
29590     registerType: function(name, config) {
29591         var proto = config.prototype,
29592             model;
29593         if (proto && proto.isModel) {
29594             // registering an already defined model
29595             model = config;
29596         } else {
29597             // passing in a configuration
29598             if (!config.extend) {
29599                 config.extend = 'Ext.data.Model';
29600             }
29601             model = Ext.define(name, config);
29602         }
29603         this.types[name] = model;
29604         return model;
29605     },
29606
29607     /**
29608      * @private
29609      * Private callback called whenever a model has just been defined. This sets up any associations
29610      * that were waiting for the given model to be defined
29611      * @param {Function} model The model that was just created
29612      */
29613     onModelDefined: function(model) {
29614         var stack  = this.associationStack,
29615             length = stack.length,
29616             create = [],
29617             association, i, created;
29618
29619         for (i = 0; i < length; i++) {
29620             association = stack[i];
29621
29622             if (association.associatedModel == model.modelName) {
29623                 create.push(association);
29624             }
29625         }
29626
29627         for (i = 0, length = create.length; i < length; i++) {
29628             created = create[i];
29629             this.types[created.ownerModel].prototype.associations.add(Ext.data.Association.create(created));
29630             Ext.Array.remove(stack, created);
29631         }
29632     },
29633
29634     /**
29635      * Registers an association where one of the models defined doesn't exist yet.
29636      * The ModelManager will check when new models are registered if it can link them
29637      * together
29638      * @private
29639      * @param {Ext.data.Association} association The association
29640      */
29641     registerDeferredAssociation: function(association){
29642         this.associationStack.push(association);
29643     },
29644
29645     /**
29646      * Returns the {@link Ext.data.Model} for a given model name
29647      * @param {String/Object} id The id of the model or the model instance.
29648      * @return {Ext.data.Model} a model class.
29649      */
29650     getModel: function(id) {
29651         var model = id;
29652         if (typeof model == 'string') {
29653             model = this.types[model];
29654         }
29655         return model;
29656     },
29657
29658     /**
29659      * Creates a new instance of a Model using the given data.
29660      *
29661      * This method is deprecated.  Use {@link Ext#create Ext.create} instead.  For example:
29662      *
29663      *     Ext.create('User', {
29664      *         first: 'Ed',
29665      *         last: 'Spencer'
29666      *     });
29667      *
29668      * @param {Object} data Data to initialize the Model's fields with
29669      * @param {String} name The name of the model to create
29670      * @param {Number} id (Optional) unique id of the Model instance (see {@link Ext.data.Model})
29671      */
29672     create: function(config, name, id) {
29673         var con = typeof name == 'function' ? name : this.types[name || config.name];
29674
29675         return new con(config, id);
29676     }
29677 }, function() {
29678
29679     /**
29680      * Old way for creating Model classes.  Instead use:
29681      *
29682      *     Ext.define("MyModel", {
29683      *         extend: "Ext.data.Model",
29684      *         fields: []
29685      *     });
29686      *
29687      * @param {String} name Name of the Model class.
29688      * @param {Object} config A configuration object for the Model you wish to create.
29689      * @return {Ext.data.Model} The newly registered Model
29690      * @member Ext
29691      * @deprecated 4.0.0 Use {@link Ext#define} instead.
29692      */
29693     Ext.regModel = function() {
29694         if (Ext.isDefined(Ext.global.console)) {
29695             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: []});.');
29696         }
29697         return this.ModelManager.registerType.apply(this.ModelManager, arguments);
29698     };
29699 });
29700
29701 /**
29702  * @singleton
29703  *
29704  * Provides a registry of available Plugin classes indexed by a mnemonic code known as the Plugin's ptype.
29705  *
29706  * A plugin may be specified simply as a *config object* as long as the correct `ptype` is specified:
29707  *
29708  *     {
29709  *         ptype: 'gridviewdragdrop',
29710  *         dragText: 'Drag and drop to reorganize'
29711  *     }
29712  *
29713  * Or just use the ptype on its own:
29714  *
29715  *     'gridviewdragdrop'
29716  *
29717  * Alternatively you can instantiate the plugin with Ext.create:
29718  *
29719  *     Ext.create('Ext.view.plugin.AutoComplete', {
29720  *         ptype: 'gridviewdragdrop',
29721  *         dragText: 'Drag and drop to reorganize'
29722  *     })
29723  */
29724 Ext.define('Ext.PluginManager', {
29725     extend: 'Ext.AbstractManager',
29726     alternateClassName: 'Ext.PluginMgr',
29727     singleton: true,
29728     typeName: 'ptype',
29729
29730     /**
29731      * Creates a new Plugin from the specified config object using the config object's ptype to determine the class to
29732      * instantiate.
29733      * @param {Object} config A configuration object for the Plugin you wish to create.
29734      * @param {Function} defaultType (optional) The constructor to provide the default Plugin type if the config object does not
29735      * contain a `ptype`. (Optional if the config contains a `ptype`).
29736      * @return {Ext.Component} The newly instantiated Plugin.
29737      */
29738     //create: function(plugin, defaultType) {
29739     //    if (plugin instanceof this) {
29740     //        return plugin;
29741     //    } else {
29742     //        var type, config = {};
29743     //
29744     //        if (Ext.isString(plugin)) {
29745     //            type = plugin;
29746     //        }
29747     //        else {
29748     //            type = plugin[this.typeName] || defaultType;
29749     //            config = plugin;
29750     //        }
29751     //
29752     //        return Ext.createByAlias('plugin.' + type, config);
29753     //    }
29754     //},
29755
29756     create : function(config, defaultType){
29757         if (config.init) {
29758             return config;
29759         } else {
29760             return Ext.createByAlias('plugin.' + (config.ptype || defaultType), config);
29761         }
29762
29763         // Prior system supported Singleton plugins.
29764         //var PluginCls = this.types[config.ptype || defaultType];
29765         //if (PluginCls.init) {
29766         //    return PluginCls;
29767         //} else {
29768         //    return new PluginCls(config);
29769         //}
29770     },
29771
29772     /**
29773      * Returns all plugins registered with the given type. Here, 'type' refers to the type of plugin, not its ptype.
29774      * @param {String} type The type to search for
29775      * @param {Boolean} defaultsOnly True to only return plugins of this type where the plugin's isDefault property is
29776      * truthy
29777      * @return {Ext.AbstractPlugin[]} All matching plugins
29778      */
29779     findByType: function(type, defaultsOnly) {
29780         var matches = [],
29781             types   = this.types;
29782
29783         for (var name in types) {
29784             if (!types.hasOwnProperty(name)) {
29785                 continue;
29786             }
29787             var item = types[name];
29788
29789             if (item.type == type && (!defaultsOnly || (defaultsOnly === true && item.isDefault))) {
29790                 matches.push(item);
29791             }
29792         }
29793
29794         return matches;
29795     }
29796 }, function() {
29797     /**
29798      * Shorthand for {@link Ext.PluginManager#registerType}
29799      * @param {String} ptype The ptype mnemonic string by which the Plugin class
29800      * may be looked up.
29801      * @param {Function} cls The new Plugin class.
29802      * @member Ext
29803      * @method preg
29804      */
29805     Ext.preg = function() {
29806         return Ext.PluginManager.registerType.apply(Ext.PluginManager, arguments);
29807     };
29808 });
29809
29810 /**
29811  * Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
29812  *
29813  * An instance of this class may be created by passing to the constructor either a single argument, or multiple
29814  * arguments:
29815  *
29816  * # Single argument: String/Array
29817  *
29818  * The single argument may be either a String or an Array:
29819  *
29820  * - String:
29821  *
29822  *       var t = new Ext.Template("<div>Hello {0}.</div>");
29823  *       t.{@link #append}('some-element', ['foo']);
29824  *
29825  * - Array:
29826  *
29827  *   An Array will be combined with `join('')`.
29828  *
29829  *       var t = new Ext.Template([
29830  *           '<div name="{id}">',
29831  *               '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
29832  *           '</div>',
29833  *       ]);
29834  *       t.{@link #compile}();
29835  *       t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
29836  *
29837  * # Multiple arguments: String, Object, Array, ...
29838  *
29839  * Multiple arguments will be combined with `join('')`.
29840  *
29841  *     var t = new Ext.Template(
29842  *         '<div name="{id}">',
29843  *             '<span class="{cls}">{name} {value}</span>',
29844  *         '</div>',
29845  *         // a configuration object:
29846  *         {
29847  *             compiled: true,      // {@link #compile} immediately
29848  *         }
29849  *     );
29850  *
29851  * # Notes
29852  *
29853  * - For a list of available format functions, see {@link Ext.util.Format}.
29854  * - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
29855  */
29856 Ext.define('Ext.Template', {
29857
29858     /* Begin Definitions */
29859
29860     requires: ['Ext.DomHelper', 'Ext.util.Format'],
29861
29862     inheritableStatics: {
29863         /**
29864          * Creates a template from the passed element's value (_display:none_ textarea, preferred) or innerHTML.
29865          * @param {String/HTMLElement} el A DOM element or its id
29866          * @param {Object} config (optional) Config object
29867          * @return {Ext.Template} The created template
29868          * @static
29869          * @inheritable
29870          */
29871         from: function(el, config) {
29872             el = Ext.getDom(el);
29873             return new this(el.value || el.innerHTML, config || '');
29874         }
29875     },
29876
29877     /* End Definitions */
29878
29879     /**
29880      * Creates new template.
29881      * 
29882      * @param {String...} html List of strings to be concatenated into template.
29883      * Alternatively an array of strings can be given, but then no config object may be passed.
29884      * @param {Object} config (optional) Config object
29885      */
29886     constructor: function(html) {
29887         var me = this,
29888             args = arguments,
29889             buffer = [],
29890             i = 0,
29891             length = args.length,
29892             value;
29893
29894         me.initialConfig = {};
29895
29896         if (length > 1) {
29897             for (; i < length; i++) {
29898                 value = args[i];
29899                 if (typeof value == 'object') {
29900                     Ext.apply(me.initialConfig, value);
29901                     Ext.apply(me, value);
29902                 } else {
29903                     buffer.push(value);
29904                 }
29905             }
29906             html = buffer.join('');
29907         } else {
29908             if (Ext.isArray(html)) {
29909                 buffer.push(html.join(''));
29910             } else {
29911                 buffer.push(html);
29912             }
29913         }
29914
29915         // @private
29916         me.html = buffer.join('');
29917
29918         if (me.compiled) {
29919             me.compile();
29920         }
29921     },
29922
29923     isTemplate: true,
29924
29925     /**
29926      * @cfg {Boolean} compiled
29927      * True to immediately compile the template. Defaults to false.
29928      */
29929
29930     /**
29931      * @cfg {Boolean} disableFormats
29932      * True to disable format functions in the template. If the template doesn't contain
29933      * format functions, setting disableFormats to true will reduce apply time. Defaults to false.
29934      */
29935     disableFormats: false,
29936
29937     re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
29938
29939     /**
29940      * Returns an HTML fragment of this template with the specified values applied.
29941      *
29942      * @param {Object/Array} values The template values. Can be an array if your params are numeric:
29943      *
29944      *     var tpl = new Ext.Template('Name: {0}, Age: {1}');
29945      *     tpl.applyTemplate(['John', 25]);
29946      *
29947      * or an object:
29948      *
29949      *     var tpl = new Ext.Template('Name: {name}, Age: {age}');
29950      *     tpl.applyTemplate({name: 'John', age: 25});
29951      *
29952      * @return {String} The HTML fragment
29953      */
29954     applyTemplate: function(values) {
29955         var me = this,
29956             useFormat = me.disableFormats !== true,
29957             fm = Ext.util.Format,
29958             tpl = me;
29959
29960         if (me.compiled) {
29961             return me.compiled(values);
29962         }
29963         function fn(m, name, format, args) {
29964             if (format && useFormat) {
29965                 if (args) {
29966                     args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
29967                 } else {
29968                     args = [values[name]];
29969                 }
29970                 if (format.substr(0, 5) == "this.") {
29971                     return tpl[format.substr(5)].apply(tpl, args);
29972                 }
29973                 else {
29974                     return fm[format].apply(fm, args);
29975                 }
29976             }
29977             else {
29978                 return values[name] !== undefined ? values[name] : "";
29979             }
29980         }
29981         return me.html.replace(me.re, fn);
29982     },
29983
29984     /**
29985      * Sets the HTML used as the template and optionally compiles it.
29986      * @param {String} html
29987      * @param {Boolean} compile (optional) True to compile the template.
29988      * @return {Ext.Template} this
29989      */
29990     set: function(html, compile) {
29991         var me = this;
29992         me.html = html;
29993         me.compiled = null;
29994         return compile ? me.compile() : me;
29995     },
29996
29997     compileARe: /\\/g,
29998     compileBRe: /(\r\n|\n)/g,
29999     compileCRe: /'/g,
30000
30001     /**
30002      * Compiles the template into an internal function, eliminating the RegEx overhead.
30003      * @return {Ext.Template} this
30004      */
30005     compile: function() {
30006         var me = this,
30007             fm = Ext.util.Format,
30008             useFormat = me.disableFormats !== true,
30009             body, bodyReturn;
30010
30011         function fn(m, name, format, args) {
30012             if (format && useFormat) {
30013                 args = args ? ',' + args: "";
30014                 if (format.substr(0, 5) != "this.") {
30015                     format = "fm." + format + '(';
30016                 }
30017                 else {
30018                     format = 'this.' + format.substr(5) + '(';
30019                 }
30020             }
30021             else {
30022                 args = '';
30023                 format = "(values['" + name + "'] == undefined ? '' : ";
30024             }
30025             return "'," + format + "values['" + name + "']" + args + ") ,'";
30026         }
30027
30028         bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
30029         body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
30030         eval(body);
30031         return me;
30032     },
30033
30034     /**
30035      * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
30036      *
30037      * @param {String/HTMLElement/Ext.Element} el The context element
30038      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
30039      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
30040      * @return {HTMLElement/Ext.Element} The new node or Element
30041      */
30042     insertFirst: function(el, values, returnElement) {
30043         return this.doInsert('afterBegin', el, values, returnElement);
30044     },
30045
30046     /**
30047      * Applies the supplied values to the template and inserts the new node(s) before el.
30048      *
30049      * @param {String/HTMLElement/Ext.Element} el The context element
30050      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
30051      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
30052      * @return {HTMLElement/Ext.Element} The new node or Element
30053      */
30054     insertBefore: function(el, values, returnElement) {
30055         return this.doInsert('beforeBegin', el, values, returnElement);
30056     },
30057
30058     /**
30059      * Applies the supplied values to the template and inserts the new node(s) after el.
30060      *
30061      * @param {String/HTMLElement/Ext.Element} el The context element
30062      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
30063      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
30064      * @return {HTMLElement/Ext.Element} The new node or Element
30065      */
30066     insertAfter: function(el, values, returnElement) {
30067         return this.doInsert('afterEnd', el, values, returnElement);
30068     },
30069
30070     /**
30071      * Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
30072      *
30073      * For example usage see {@link Ext.Template Ext.Template class docs}.
30074      *
30075      * @param {String/HTMLElement/Ext.Element} el The context element
30076      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
30077      * @param {Boolean} returnElement (optional) true to return an Ext.Element.
30078      * @return {HTMLElement/Ext.Element} The new node or Element
30079      */
30080     append: function(el, values, returnElement) {
30081         return this.doInsert('beforeEnd', el, values, returnElement);
30082     },
30083
30084     doInsert: function(where, el, values, returnEl) {
30085         el = Ext.getDom(el);
30086         var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));
30087         return returnEl ? Ext.get(newNode, true) : newNode;
30088     },
30089
30090     /**
30091      * Applies the supplied values to the template and overwrites the content of el with the new node(s).
30092      *
30093      * @param {String/HTMLElement/Ext.Element} el The context element
30094      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
30095      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
30096      * @return {HTMLElement/Ext.Element} The new node or Element
30097      */
30098     overwrite: function(el, values, returnElement) {
30099         el = Ext.getDom(el);
30100         el.innerHTML = this.applyTemplate(values);
30101         return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
30102     }
30103 }, function() {
30104
30105     /**
30106      * @method apply
30107      * @member Ext.Template
30108      * Alias for {@link #applyTemplate}.
30109      * @alias Ext.Template#applyTemplate
30110      */
30111     this.createAlias('apply', 'applyTemplate');
30112 });
30113
30114 /**
30115  * A template class that supports advanced functionality like:
30116  *
30117  * - Autofilling arrays using templates and sub-templates
30118  * - Conditional processing with basic comparison operators
30119  * - Basic math function support
30120  * - Execute arbitrary inline code with special built-in template variables
30121  * - Custom member functions
30122  * - 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
30123  *
30124  * XTemplate provides the templating mechanism built into:
30125  *
30126  * - {@link Ext.view.View}
30127  *
30128  * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
30129  * demonstrate all of the supported features.
30130  *
30131  * # Sample Data
30132  *
30133  * This is the data object used for reference in each code example:
30134  *
30135  *     var data = {
30136  *         name: 'Tommy Maintz',
30137  *         title: 'Lead Developer',
30138  *         company: 'Sencha Inc.',
30139  *         email: 'tommy@sencha.com',
30140  *         address: '5 Cups Drive',
30141  *         city: 'Palo Alto',
30142  *         state: 'CA',
30143  *         zip: '44102',
30144  *         drinks: ['Coffee', 'Soda', 'Water'],
30145  *         kids: [
30146  *             {
30147  *                 name: 'Joshua',
30148  *                 age:3
30149  *             },
30150  *             {
30151  *                 name: 'Matthew',
30152  *                 age:2
30153  *             },
30154  *             {
30155  *                 name: 'Solomon',
30156  *                 age:0
30157  *             }
30158  *         ]
30159  *     };
30160  *
30161  * # Auto filling of arrays
30162  *
30163  * The **tpl** tag and the **for** operator are used to process the provided data object:
30164  *
30165  * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
30166  *   tag for each item in the array.
30167  * - If for="." is specified, the data object provided is examined.
30168  * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
30169  *
30170  * Examples:
30171  *
30172  *     <tpl for=".">...</tpl>       // loop through array at root node
30173  *     <tpl for="foo">...</tpl>     // loop through array at foo node
30174  *     <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
30175  *
30176  * Using the sample data above:
30177  *
30178  *     var tpl = new Ext.XTemplate(
30179  *         '<p>Kids: ',
30180  *         '<tpl for=".">',       // process the data.kids node
30181  *             '<p>{#}. {name}</p>',  // use current array index to autonumber
30182  *         '</tpl></p>'
30183  *     );
30184  *     tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
30185  *
30186  * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
30187  * object to populate the template:
30188  *
30189  *     var tpl = new Ext.XTemplate(
30190  *         '<p>Name: {name}</p>',
30191  *         '<p>Title: {title}</p>',
30192  *         '<p>Company: {company}</p>',
30193  *         '<p>Kids: ',
30194  *         '<tpl for="kids">',     // interrogate the kids property within the data
30195  *             '<p>{name}</p>',
30196  *         '</tpl></p>'
30197  *     );
30198  *     tpl.overwrite(panel.body, data);  // pass the root node of the data object
30199  *
30200  * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
30201  * loop. This variable will represent the value of the array at the current index:
30202  *
30203  *     var tpl = new Ext.XTemplate(
30204  *         '<p>{name}\'s favorite beverages:</p>',
30205  *         '<tpl for="drinks">',
30206  *             '<div> - {.}</div>',
30207  *         '</tpl>'
30208  *     );
30209  *     tpl.overwrite(panel.body, data);
30210  *
30211  * When processing a sub-template, for example while looping through a child array, you can access the parent object's
30212  * members via the **parent** object:
30213  *
30214  *     var tpl = new Ext.XTemplate(
30215  *         '<p>Name: {name}</p>',
30216  *         '<p>Kids: ',
30217  *         '<tpl for="kids">',
30218  *             '<tpl if="age &gt; 1">',
30219  *                 '<p>{name}</p>',
30220  *                 '<p>Dad: {parent.name}</p>',
30221  *             '</tpl>',
30222  *         '</tpl></p>'
30223  *     );
30224  *     tpl.overwrite(panel.body, data);
30225  *
30226  * # Conditional processing with basic comparison operators
30227  *
30228  * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
30229  * specific parts of the template. Notes:
30230  *
30231  * - Double quotes must be encoded if used within the conditional
30232  * - There is no else operator -- if needed, two opposite if statements should be used.
30233  *
30234  * Examples:
30235  *
30236  *     <tpl if="age > 1 && age < 10">Child</tpl>
30237  *     <tpl if="age >= 10 && age < 18">Teenager</tpl>
30238  *     <tpl if="this.isGirl(name)">...</tpl>
30239  *     <tpl if="id==\'download\'">...</tpl>
30240  *     <tpl if="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
30241  *     // no good:
30242  *     <tpl if="name == "Tommy"">Hello</tpl>
30243  *     // encode " if it is part of the condition, e.g.
30244  *     <tpl if="name == &quot;Tommy&quot;">Hello</tpl>
30245  *
30246  * Using the sample data above:
30247  *
30248  *     var tpl = new Ext.XTemplate(
30249  *         '<p>Name: {name}</p>',
30250  *         '<p>Kids: ',
30251  *         '<tpl for="kids">',
30252  *             '<tpl if="age &gt; 1">',
30253  *                 '<p>{name}</p>',
30254  *             '</tpl>',
30255  *         '</tpl></p>'
30256  *     );
30257  *     tpl.overwrite(panel.body, data);
30258  *
30259  * # Basic math support
30260  *
30261  * The following basic math operators may be applied directly on numeric data values:
30262  *
30263  *     + - * /
30264  *
30265  * For example:
30266  *
30267  *     var tpl = new Ext.XTemplate(
30268  *         '<p>Name: {name}</p>',
30269  *         '<p>Kids: ',
30270  *         '<tpl for="kids">',
30271  *             '<tpl if="age &gt; 1">',  // <-- Note that the > is encoded
30272  *                 '<p>{#}: {name}</p>',  // <-- Auto-number each item
30273  *                 '<p>In 5 Years: {age+5}</p>',  // <-- Basic math
30274  *                 '<p>Dad: {parent.name}</p>',
30275  *             '</tpl>',
30276  *         '</tpl></p>'
30277  *     );
30278  *     tpl.overwrite(panel.body, data);
30279  *
30280  * # Execute arbitrary inline code with special built-in template variables
30281  *
30282  * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template. There are some special
30283  * variables available in that code:
30284  *
30285  * - **values**: The values in the current scope. If you are using scope changing sub-templates,
30286  *   you can change what values is.
30287  * - **parent**: The scope (values) of the ancestor template.
30288  * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
30289  * - **xcount**: If you are in a looping template, the total length of the array you are looping.
30290  *
30291  * This example demonstrates basic row striping using an inline code block and the xindex variable:
30292  *
30293  *     var tpl = new Ext.XTemplate(
30294  *         '<p>Name: {name}</p>',
30295  *         '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
30296  *         '<p>Kids: ',
30297  *         '<tpl for="kids">',
30298  *             '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
30299  *             '{name}',
30300  *             '</div>',
30301  *         '</tpl></p>'
30302  *      );
30303  *     tpl.overwrite(panel.body, data);
30304  *
30305  * # Template member functions
30306  *
30307  * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
30308  * more complex processing:
30309  *
30310  *     var tpl = new Ext.XTemplate(
30311  *         '<p>Name: {name}</p>',
30312  *         '<p>Kids: ',
30313  *         '<tpl for="kids">',
30314  *             '<tpl if="this.isGirl(name)">',
30315  *                 '<p>Girl: {name} - {age}</p>',
30316  *             '</tpl>',
30317  *              // use opposite if statement to simulate 'else' processing:
30318  *             '<tpl if="this.isGirl(name) == false">',
30319  *                 '<p>Boy: {name} - {age}</p>',
30320  *             '</tpl>',
30321  *             '<tpl if="this.isBaby(age)">',
30322  *                 '<p>{name} is a baby!</p>',
30323  *             '</tpl>',
30324  *         '</tpl></p>',
30325  *         {
30326  *             // XTemplate configuration:
30327  *             disableFormats: true,
30328  *             // member functions:
30329  *             isGirl: function(name){
30330  *                return name == 'Sara Grace';
30331  *             },
30332  *             isBaby: function(age){
30333  *                return age < 1;
30334  *             }
30335  *         }
30336  *     );
30337  *     tpl.overwrite(panel.body, data);
30338  */
30339 Ext.define('Ext.XTemplate', {
30340
30341     /* Begin Definitions */
30342
30343     extend: 'Ext.Template',
30344
30345     /* End Definitions */
30346
30347     argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
30348     nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
30349     ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
30350     execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
30351     constructor: function() {
30352         this.callParent(arguments);
30353
30354         var me = this,
30355             html = me.html,
30356             argsRe = me.argsRe,
30357             nameRe = me.nameRe,
30358             ifRe = me.ifRe,
30359             execRe = me.execRe,
30360             id = 0,
30361             tpls = [],
30362             VALUES = 'values',
30363             PARENT = 'parent',
30364             XINDEX = 'xindex',
30365             XCOUNT = 'xcount',
30366             RETURN = 'return ',
30367             WITHVALUES = 'with(values){ ',
30368             m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
30369
30370         html = ['<tpl>', html, '</tpl>'].join('');
30371
30372         while ((m = html.match(argsRe))) {
30373             exp = null;
30374             fn = null;
30375             exec = null;
30376             matchName = m[0].match(nameRe);
30377             matchIf = m[0].match(ifRe);
30378             matchExec = m[0].match(execRe);
30379
30380             exp = matchIf ? matchIf[1] : null;
30381             if (exp) {
30382                 fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
30383             }
30384
30385             exp = matchExec ? matchExec[1] : null;
30386             if (exp) {
30387                 exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
30388             }
30389
30390             name = matchName ? matchName[1] : null;
30391             if (name) {
30392                 if (name === '.') {
30393                     name = VALUES;
30394                 } else if (name === '..') {
30395                     name = PARENT;
30396                 }
30397                 name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
30398             }
30399
30400             tpls.push({
30401                 id: id,
30402                 target: name,
30403                 exec: exec,
30404                 test: fn,
30405                 body: m[1] || ''
30406             });
30407
30408             html = html.replace(m[0], '{xtpl' + id + '}');
30409             id = id + 1;
30410         }
30411
30412         for (i = tpls.length - 1; i >= 0; --i) {
30413             me.compileTpl(tpls[i]);
30414         }
30415         me.master = tpls[tpls.length - 1];
30416         me.tpls = tpls;
30417     },
30418
30419     // @private
30420     applySubTemplate: function(id, values, parent, xindex, xcount) {
30421         var me = this, t = me.tpls[id];
30422         return t.compiled.call(me, values, parent, xindex, xcount);
30423     },
30424
30425     /**
30426      * @cfg {RegExp} codeRe
30427      * The regular expression used to match code variables. Default: matches {[expression]}.
30428      */
30429     codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
30430
30431     /**
30432      * @cfg {Boolean} compiled
30433      * Only applies to {@link Ext.Template}, XTemplates are compiled automatically.
30434      */
30435
30436     re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
30437
30438     // @private
30439     compileTpl: function(tpl) {
30440         var fm = Ext.util.Format,
30441             me = this,
30442             useFormat = me.disableFormats !== true,
30443             body, bodyReturn, evaluatedFn;
30444
30445         function fn(m, name, format, args, math) {
30446             var v;
30447             // name is what is inside the {}
30448             // Name begins with xtpl, use a Sub Template
30449             if (name.substr(0, 4) == 'xtpl') {
30450                 return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
30451             }
30452             // name = "." - Just use the values object.
30453             if (name == '.') {
30454                 // filter to not include arrays/objects/nulls
30455                 v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
30456             }
30457
30458             // name = "#" - Use the xindex
30459             else if (name == '#') {
30460                 v = 'xindex';
30461             }
30462             else if (name.substr(0, 7) == "parent.") {
30463                 v = name;
30464             }
30465             // name has a . in it - Use object literal notation, starting from values
30466             else if (name.indexOf('.') != -1) {
30467                 v = "values." + name;
30468             }
30469
30470             // name is a property of values
30471             else {
30472                 v = "values['" + name + "']";
30473             }
30474             if (math) {
30475                 v = '(' + v + math + ')';
30476             }
30477             if (format && useFormat) {
30478                 args = args ? ',' + args : "";
30479                 if (format.substr(0, 5) != "this.") {
30480                     format = "fm." + format + '(';
30481                 }
30482                 else {
30483                     format = 'this.' + format.substr(5) + '(';
30484                 }
30485             }
30486             else {
30487                 args = '';
30488                 format = "(" + v + " === undefined ? '' : ";
30489             }
30490             return "'," + format + v + args + "),'";
30491         }
30492
30493         function codeFn(m, code) {
30494             // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
30495             return "',(" + code.replace(me.compileARe, "'") + "),'";
30496         }
30497
30498         bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
30499         body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
30500         eval(body);
30501
30502         tpl.compiled = function(values, parent, xindex, xcount) {
30503             var vs,
30504                 length,
30505                 buffer,
30506                 i;
30507
30508             if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
30509                 return '';
30510             }
30511
30512             vs = tpl.target ? tpl.target.call(me, values, parent) : values;
30513             if (!vs) {
30514                return '';
30515             }
30516
30517             parent = tpl.target ? values : parent;
30518             if (tpl.target && Ext.isArray(vs)) {
30519                 buffer = [];
30520                 length = vs.length;
30521                 if (tpl.exec) {
30522                     for (i = 0; i < length; i++) {
30523                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
30524                         tpl.exec.call(me, vs[i], parent, i + 1, length);
30525                     }
30526                 } else {
30527                     for (i = 0; i < length; i++) {
30528                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
30529                     }
30530                 }
30531                 return buffer.join('');
30532             }
30533
30534             if (tpl.exec) {
30535                 tpl.exec.call(me, vs, parent, xindex, xcount);
30536             }
30537             return evaluatedFn.call(me, vs, parent, xindex, xcount);
30538         };
30539
30540         return this;
30541     },
30542
30543     // inherit docs from Ext.Template
30544     applyTemplate: function(values) {
30545         return this.master.compiled.call(this, values, {}, 1, 1);
30546     },
30547
30548     /**
30549      * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
30550      * @return {Ext.XTemplate} this
30551      */
30552     compile: function() {
30553         return this;
30554     }
30555 }, function() {
30556     // re-create the alias, inheriting it from Ext.Template doesn't work as intended.
30557     this.createAlias('apply', 'applyTemplate');
30558 });
30559
30560 /**
30561  * @class Ext.app.Controller
30562  *
30563  * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
30564  * views) and take some action. Here's how we might create a Controller to manage Users:
30565  *
30566  *     Ext.define('MyApp.controller.Users', {
30567  *         extend: 'Ext.app.Controller',
30568  *
30569  *         init: function() {
30570  *             console.log('Initialized Users! This happens before the Application launch function is called');
30571  *         }
30572  *     });
30573  *
30574  * The init function is a special method that is called when your application boots. It is called before the
30575  * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
30576  * your Viewport is created.
30577  *
30578  * The init function is a great place to set up how your controller interacts with the view, and is usually used in
30579  * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function
30580  * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
30581  * our Users controller to tell us when the panel is rendered:
30582  *
30583  *     Ext.define('MyApp.controller.Users', {
30584  *         extend: 'Ext.app.Controller',
30585  *
30586  *         init: function() {
30587  *             this.control({
30588  *                 'viewport > panel': {
30589  *                     render: this.onPanelRendered
30590  *                 }
30591  *             });
30592  *         },
30593  *
30594  *         onPanelRendered: function() {
30595  *             console.log('The panel was rendered');
30596  *         }
30597  *     });
30598  *
30599  * We've updated the init function to use this.control to set up listeners on views in our application. The control
30600  * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
30601  * are not familiar with ComponentQuery yet, be sure to check out the {@link Ext.ComponentQuery documentation}. In brief though,
30602  * it allows us to pass a CSS-like selector that will find every matching component on the page.
30603  *
30604  * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
30605  * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler
30606  * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our
30607  * onPanelRendered function is called.
30608  *
30609  * <u>Using refs</u>
30610  *
30611  * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
30612  * make it really easy to get references to Views on your page. Let's look at an example of this now:
30613  *
30614  *     Ext.define('MyApp.controller.Users', {
30615  *         extend: 'Ext.app.Controller',
30616  *
30617  *         refs: [
30618  *             {
30619  *                 ref: 'list',
30620  *                 selector: 'grid'
30621  *             }
30622  *         ],
30623  *
30624  *         init: function() {
30625  *             this.control({
30626  *                 'button': {
30627  *                     click: this.refreshGrid
30628  *                 }
30629  *             });
30630  *         },
30631  *
30632  *         refreshGrid: function() {
30633  *             this.getList().store.load();
30634  *         }
30635  *     });
30636  *
30637  * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to
30638  * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this -
30639  * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
30640  * assigns it to the reference 'list'.
30641  *
30642  * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
30643  * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which
30644  * was capitalized and prepended with get to go from 'list' to 'getList'.
30645  *
30646  * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
30647  * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will
30648  * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
30649  * match a single View in your application (in the case above our selector will match any grid on the page).
30650  *
30651  * Bringing it all together, our init function is called when the application boots, at which time we call this.control
30652  * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will
30653  * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
30654  * simplicity). When the button is clicked we use out getList function to refresh the grid.
30655  *
30656  * You can create any number of refs and control any number of components this way, simply adding more functions to
30657  * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the
30658  * examples/app/feed-viewer folder in the SDK download.
30659  *
30660  * <u>Generated getter methods</u>
30661  *
30662  * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and
30663  * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
30664  *
30665  *     Ext.define('MyApp.controller.Users', {
30666  *         extend: 'Ext.app.Controller',
30667  *
30668  *         models: ['User'],
30669  *         stores: ['AllUsers', 'AdminUsers'],
30670  *
30671  *         init: function() {
30672  *             var User = this.getUserModel(),
30673  *                 allUsers = this.getAllUsersStore();
30674  *
30675  *             var ed = new User({name: 'Ed'});
30676  *             allUsers.add(ed);
30677  *         }
30678  *     });
30679  *
30680  * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
30681  * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter
30682  * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
30683  * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the
30684  * functionality.
30685  *
30686  * <u>Further Reading</u>
30687  *
30688  * For more information about writing Ext JS 4 applications, please see the
30689  * [application architecture guide](#/guide/application_architecture). Also see the {@link Ext.app.Application} documentation.
30690  *
30691  * @docauthor Ed Spencer
30692  */
30693 Ext.define('Ext.app.Controller', {
30694
30695     mixins: {
30696         observable: 'Ext.util.Observable'
30697     },
30698
30699     /**
30700      * @cfg {String} id The id of this controller. You can use this id when dispatching.
30701      */
30702     
30703     /**
30704      * @cfg {String[]} models
30705      * Array of models to require from AppName.model namespace. For example:
30706      * 
30707      *     Ext.define("MyApp.controller.Foo", {
30708      *         extend: "Ext.app.Controller",
30709      *         models: ['User', 'Vehicle']
30710      *     });
30711      * 
30712      * This is equivalent of:
30713      * 
30714      *     Ext.define("MyApp.controller.Foo", {
30715      *         extend: "Ext.app.Controller",
30716      *         requires: ['MyApp.model.User', 'MyApp.model.Vehicle']
30717      *     });
30718      * 
30719      */
30720
30721     /**
30722      * @cfg {String[]} views
30723      * Array of views to require from AppName.view namespace. For example:
30724      * 
30725      *     Ext.define("MyApp.controller.Foo", {
30726      *         extend: "Ext.app.Controller",
30727      *         views: ['List', 'Detail']
30728      *     });
30729      * 
30730      * This is equivalent of:
30731      * 
30732      *     Ext.define("MyApp.controller.Foo", {
30733      *         extend: "Ext.app.Controller",
30734      *         requires: ['MyApp.view.List', 'MyApp.view.Detail']
30735      *     });
30736      * 
30737      */
30738
30739     /**
30740      * @cfg {String[]} stores
30741      * Array of stores to require from AppName.store namespace. For example:
30742      * 
30743      *     Ext.define("MyApp.controller.Foo", {
30744      *         extend: "Ext.app.Controller",
30745      *         stores: ['Users', 'Vehicles']
30746      *     });
30747      * 
30748      * This is equivalent of:
30749      * 
30750      *     Ext.define("MyApp.controller.Foo", {
30751      *         extend: "Ext.app.Controller",
30752      *         requires: ['MyApp.store.Users', 'MyApp.store.Vehicles']
30753      *     });
30754      * 
30755      */
30756
30757     onClassExtended: function(cls, data) {
30758         var className = Ext.getClassName(cls),
30759             match = className.match(/^(.*)\.controller\./);
30760
30761         if (match !== null) {
30762             var namespace = Ext.Loader.getPrefix(className) || match[1],
30763                 onBeforeClassCreated = data.onBeforeClassCreated,
30764                 requires = [],
30765                 modules = ['model', 'view', 'store'],
30766                 prefix;
30767
30768             data.onBeforeClassCreated = function(cls, data) {
30769                 var i, ln, module,
30770                     items, j, subLn, item;
30771
30772                 for (i = 0,ln = modules.length; i < ln; i++) {
30773                     module = modules[i];
30774
30775                     items = Ext.Array.from(data[module + 's']);
30776
30777                     for (j = 0,subLn = items.length; j < subLn; j++) {
30778                         item = items[j];
30779
30780                         prefix = Ext.Loader.getPrefix(item);
30781
30782                         if (prefix === '' || prefix === item) {
30783                             requires.push(namespace + '.' + module + '.' + item);
30784                         }
30785                         else {
30786                             requires.push(item);
30787                         }
30788                     }
30789                 }
30790
30791                 Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
30792             };
30793         }
30794     },
30795
30796     /**
30797      * Creates new Controller.
30798      * @param {Object} config (optional) Config object.
30799      */
30800     constructor: function(config) {
30801         this.mixins.observable.constructor.call(this, config);
30802
30803         Ext.apply(this, config || {});
30804
30805         this.createGetters('model', this.models);
30806         this.createGetters('store', this.stores);
30807         this.createGetters('view', this.views);
30808
30809         if (this.refs) {
30810             this.ref(this.refs);
30811         }
30812     },
30813
30814     /**
30815      * A template method that is called when your application boots. It is called before the
30816      * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
30817      * your Viewport is created.
30818      * 
30819      * @param {Ext.app.Application} application
30820      * @template
30821      */
30822     init: function(application) {},
30823
30824     /**
30825      * A template method like {@link #init}, but called after the viewport is created.
30826      * This is called after the {@link Ext.app.Application#launch launch} method of Application is executed.
30827      * 
30828      * @param {Ext.app.Application} application
30829      * @template
30830      */
30831     onLaunch: function(application) {},
30832
30833     createGetters: function(type, refs) {
30834         type = Ext.String.capitalize(type);
30835         Ext.Array.each(refs, function(ref) {
30836             var fn = 'get',
30837                 parts = ref.split('.');
30838
30839             // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
30840             Ext.Array.each(parts, function(part) {
30841                 fn += Ext.String.capitalize(part);
30842             });
30843             fn += type;
30844
30845             if (!this[fn]) {
30846                 this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
30847             }
30848             // Execute it right away
30849             this[fn](ref);
30850         },
30851         this);
30852     },
30853
30854     ref: function(refs) {
30855         var me = this;
30856         refs = Ext.Array.from(refs);
30857         Ext.Array.each(refs, function(info) {
30858             var ref = info.ref,
30859                 fn = 'get' + Ext.String.capitalize(ref);
30860             if (!me[fn]) {
30861                 me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
30862             }
30863         });
30864     },
30865
30866     getRef: function(ref, info, config) {
30867         this.refCache = this.refCache || {};
30868         info = info || {};
30869         config = config || {};
30870
30871         Ext.apply(info, config);
30872
30873         if (info.forceCreate) {
30874             return Ext.ComponentManager.create(info, 'component');
30875         }
30876
30877         var me = this,
30878             selector = info.selector,
30879             cached = me.refCache[ref];
30880
30881         if (!cached) {
30882             me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
30883             if (!cached && info.autoCreate) {
30884                 me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
30885             }
30886             if (cached) {
30887                 cached.on('beforedestroy', function() {
30888                     me.refCache[ref] = null;
30889                 });
30890             }
30891         }
30892
30893         return cached;
30894     },
30895
30896     /**
30897      * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
30898      * object containing component paths mapped to a hash of listener functions.
30899      *
30900      * In the following example the `updateUser` function is mapped to to the `click`
30901      * event on a button component, which is a child of the `useredit` component.
30902      *
30903      *     Ext.define('AM.controller.Users', {
30904      *         init: function() {
30905      *             this.control({
30906      *                 'useredit button[action=save]': {
30907      *                     click: this.updateUser
30908      *                 }
30909      *             });
30910      *         },
30911      *
30912      *         updateUser: function(button) {
30913      *             console.log('clicked the Save button');
30914      *         }
30915      *     });
30916      *
30917      * See {@link Ext.ComponentQuery} for more information on component selectors.
30918      *
30919      * @param {String/Object} selectors If a String, the second argument is used as the
30920      * listeners, otherwise an object of selectors -> listeners is assumed
30921      * @param {Object} listeners
30922      */
30923     control: function(selectors, listeners) {
30924         this.application.control(selectors, listeners, this);
30925     },
30926
30927     /**
30928      * Returns instance of a {@link Ext.app.Controller controller} with the given name.
30929      * When controller doesn't exist yet, it's created.
30930      * @param {String} name
30931      * @return {Ext.app.Controller} a controller instance.
30932      */
30933     getController: function(name) {
30934         return this.application.getController(name);
30935     },
30936
30937     /**
30938      * Returns instance of a {@link Ext.data.Store Store} with the given name.
30939      * When store doesn't exist yet, it's created.
30940      * @param {String} name
30941      * @return {Ext.data.Store} a store instance.
30942      */
30943     getStore: function(name) {
30944         return this.application.getStore(name);
30945     },
30946
30947     /**
30948      * Returns a {@link Ext.data.Model Model} class with the given name.
30949      * A shorthand for using {@link Ext.ModelManager#getModel}.
30950      * @param {String} name
30951      * @return {Ext.data.Model} a model class.
30952      */
30953     getModel: function(model) {
30954         return this.application.getModel(model);
30955     },
30956
30957     /**
30958      * Returns a View class with the given name.  To create an instance of the view,
30959      * you can use it like it's used by Application to create the Viewport:
30960      * 
30961      *     this.getView('Viewport').create();
30962      * 
30963      * @param {String} name
30964      * @return {Ext.Base} a view class.
30965      */
30966     getView: function(view) {
30967         return this.application.getView(view);
30968     }
30969 });
30970
30971 /**
30972  * @author Don Griffin
30973  *
30974  * This class is a base for all id generators. It also provides lookup of id generators by
30975  * their id.
30976  * 
30977  * Generally, id generators are used to generate a primary key for new model instances. There
30978  * are different approaches to solving this problem, so this mechanism has both simple use
30979  * cases and is open to custom implementations. A {@link Ext.data.Model} requests id generation
30980  * using the {@link Ext.data.Model#idgen} property.
30981  *
30982  * # Identity, Type and Shared IdGenerators
30983  *
30984  * It is often desirable to share IdGenerators to ensure uniqueness or common configuration.
30985  * This is done by giving IdGenerator instances an id property by which they can be looked
30986  * up using the {@link #get} method. To configure two {@link Ext.data.Model Model} classes
30987  * to share one {@link Ext.data.SequentialIdGenerator sequential} id generator, you simply
30988  * assign them the same id:
30989  *
30990  *     Ext.define('MyApp.data.MyModelA', {
30991  *         extend: 'Ext.data.Model',
30992  *         idgen: {
30993  *             type: 'sequential',
30994  *             id: 'foo'
30995  *         }
30996  *     });
30997  *
30998  *     Ext.define('MyApp.data.MyModelB', {
30999  *         extend: 'Ext.data.Model',
31000  *         idgen: {
31001  *             type: 'sequential',
31002  *             id: 'foo'
31003  *         }
31004  *     });
31005  *
31006  * To make this as simple as possible for generator types that are shared by many (or all)
31007  * Models, the IdGenerator types (such as 'sequential' or 'uuid') are also reserved as
31008  * generator id's. This is used by the {@link Ext.data.UuidGenerator} which has an id equal
31009  * to its type ('uuid'). In other words, the following Models share the same generator:
31010  *
31011  *     Ext.define('MyApp.data.MyModelX', {
31012  *         extend: 'Ext.data.Model',
31013  *         idgen: 'uuid'
31014  *     });
31015  *
31016  *     Ext.define('MyApp.data.MyModelY', {
31017  *         extend: 'Ext.data.Model',
31018  *         idgen: 'uuid'
31019  *     });
31020  *
31021  * This can be overridden (by specifying the id explicitly), but there is no particularly
31022  * good reason to do so for this generator type.
31023  *
31024  * # Creating Custom Generators
31025  * 
31026  * An id generator should derive from this class and implement the {@link #generate} method.
31027  * The constructor will apply config properties on new instances, so a constructor is often
31028  * not necessary.
31029  *
31030  * To register an id generator type, a derived class should provide an `alias` like so:
31031  *
31032  *     Ext.define('MyApp.data.CustomIdGenerator', {
31033  *         extend: 'Ext.data.IdGenerator',
31034  *         alias: 'idgen.custom',
31035  *
31036  *         configProp: 42, // some config property w/default value
31037  *
31038  *         generate: function () {
31039  *             return ... // a new id
31040  *         }
31041  *     });
31042  *
31043  * Using the custom id generator is then straightforward:
31044  *
31045  *     Ext.define('MyApp.data.MyModel', {
31046  *         extend: 'Ext.data.Model',
31047  *         idgen: 'custom'
31048  *     });
31049  *     // or...
31050  *
31051  *     Ext.define('MyApp.data.MyModel', {
31052  *         extend: 'Ext.data.Model',
31053  *         idgen: {
31054  *             type: 'custom',
31055  *             configProp: value
31056  *         }
31057  *     });
31058  *
31059  * It is not recommended to mix shared generators with generator configuration. This leads
31060  * to unpredictable results unless all configurations match (which is also redundant). In
31061  * such cases, a custom generator with a default id is the best approach.
31062  *
31063  *     Ext.define('MyApp.data.CustomIdGenerator', {
31064  *         extend: 'Ext.data.SequentialIdGenerator',
31065  *         alias: 'idgen.custom',
31066  *
31067  *         id: 'custom', // shared by default
31068  *
31069  *         prefix: 'ID_',
31070  *         seed: 1000
31071  *     });
31072  *
31073  *     Ext.define('MyApp.data.MyModelX', {
31074  *         extend: 'Ext.data.Model',
31075  *         idgen: 'custom'
31076  *     });
31077  *
31078  *     Ext.define('MyApp.data.MyModelY', {
31079  *         extend: 'Ext.data.Model',
31080  *         idgen: 'custom'
31081  *     });
31082  *
31083  *     // the above models share a generator that produces ID_1000, ID_1001, etc..
31084  *
31085  */
31086 Ext.define('Ext.data.IdGenerator', {
31087
31088     isGenerator: true,
31089
31090     /**
31091      * Initializes a new instance.
31092      * @param {Object} config (optional) Configuration object to be applied to the new instance.
31093      */
31094     constructor: function(config) {
31095         var me = this;
31096
31097         Ext.apply(me, config);
31098
31099         if (me.id) {
31100             Ext.data.IdGenerator.all[me.id] = me;
31101         }
31102     },
31103
31104     /**
31105      * @cfg {String} id
31106      * The id by which to register a new instance. This instance can be found using the
31107      * {@link Ext.data.IdGenerator#get} static method.
31108      */
31109
31110     getRecId: function (rec) {
31111         return rec.modelName + '-' + rec.internalId;
31112     },
31113
31114     /**
31115      * Generates and returns the next id. This method must be implemented by the derived
31116      * class.
31117      *
31118      * @return {String} The next id.
31119      * @method generate
31120      * @abstract
31121      */
31122
31123     statics: {
31124         /**
31125          * @property {Object} all
31126          * This object is keyed by id to lookup instances.
31127          * @private
31128          * @static
31129          */
31130         all: {},
31131
31132         /**
31133          * Returns the IdGenerator given its config description.
31134          * @param {String/Object} config If this parameter is an IdGenerator instance, it is
31135          * simply returned. If this is a string, it is first used as an id for lookup and
31136          * then, if there is no match, as a type to create a new instance. This parameter
31137          * can also be a config object that contains a `type` property (among others) that
31138          * are used to create and configure the instance.
31139          * @static
31140          */
31141         get: function (config) {
31142             var generator,
31143                 id,
31144                 type;
31145
31146             if (typeof config == 'string') {
31147                 id = type = config;
31148                 config = null;
31149             } else if (config.isGenerator) {
31150                 return config;
31151             } else {
31152                 id = config.id || config.type;
31153                 type = config.type;
31154             }
31155
31156             generator = this.all[id];
31157             if (!generator) {
31158                 generator = Ext.create('idgen.' + type, config);
31159             }
31160
31161             return generator;
31162         }
31163     }
31164 });
31165
31166 /**
31167  * @class Ext.data.SortTypes
31168  * This class defines a series of static methods that are used on a
31169  * {@link Ext.data.Field} for performing sorting. The methods cast the 
31170  * underlying values into a data type that is appropriate for sorting on
31171  * that particular field.  If a {@link Ext.data.Field#type} is specified, 
31172  * the sortType will be set to a sane default if the sortType is not 
31173  * explicitly defined on the field. The sortType will make any necessary
31174  * modifications to the value and return it.
31175  * <ul>
31176  * <li><b>asText</b> - Removes any tags and converts the value to a string</li>
31177  * <li><b>asUCText</b> - Removes any tags and converts the value to an uppercase string</li>
31178  * <li><b>asUCText</b> - Converts the value to an uppercase string</li>
31179  * <li><b>asDate</b> - Converts the value into Unix epoch time</li>
31180  * <li><b>asFloat</b> - Converts the value to a floating point number</li>
31181  * <li><b>asInt</b> - Converts the value to an integer number</li>
31182  * </ul>
31183  * <p>
31184  * It is also possible to create a custom sortType that can be used throughout
31185  * an application.
31186  * <pre><code>
31187 Ext.apply(Ext.data.SortTypes, {
31188     asPerson: function(person){
31189         // expects an object with a first and last name property
31190         return person.lastName.toUpperCase() + person.firstName.toLowerCase();
31191     }    
31192 });
31193
31194 Ext.define('Employee', {
31195     extend: 'Ext.data.Model',
31196     fields: [{
31197         name: 'person',
31198         sortType: 'asPerson'
31199     }, {
31200         name: 'salary',
31201         type: 'float' // sortType set to asFloat
31202     }]
31203 });
31204  * </code></pre>
31205  * </p>
31206  * @singleton
31207  * @docauthor Evan Trimboli <evan@sencha.com>
31208  */
31209 Ext.define('Ext.data.SortTypes', {
31210     
31211     singleton: true,
31212     
31213     /**
31214      * Default sort that does nothing
31215      * @param {Object} s The value being converted
31216      * @return {Object} The comparison value
31217      */
31218     none : function(s) {
31219         return s;
31220     },
31221
31222     /**
31223      * The regular expression used to strip tags
31224      * @type {RegExp}
31225      * @property
31226      */
31227     stripTagsRE : /<\/?[^>]+>/gi,
31228
31229     /**
31230      * Strips all HTML tags to sort on text only
31231      * @param {Object} s The value being converted
31232      * @return {String} The comparison value
31233      */
31234     asText : function(s) {
31235         return String(s).replace(this.stripTagsRE, "");
31236     },
31237
31238     /**
31239      * Strips all HTML tags to sort on text only - Case insensitive
31240      * @param {Object} s The value being converted
31241      * @return {String} The comparison value
31242      */
31243     asUCText : function(s) {
31244         return String(s).toUpperCase().replace(this.stripTagsRE, "");
31245     },
31246
31247     /**
31248      * Case insensitive string
31249      * @param {Object} s The value being converted
31250      * @return {String} The comparison value
31251      */
31252     asUCString : function(s) {
31253         return String(s).toUpperCase();
31254     },
31255
31256     /**
31257      * Date sorting
31258      * @param {Object} s The value being converted
31259      * @return {Number} The comparison value
31260      */
31261     asDate : function(s) {
31262         if(!s){
31263             return 0;
31264         }
31265         if(Ext.isDate(s)){
31266             return s.getTime();
31267         }
31268         return Date.parse(String(s));
31269     },
31270
31271     /**
31272      * Float sorting
31273      * @param {Object} s The value being converted
31274      * @return {Number} The comparison value
31275      */
31276     asFloat : function(s) {
31277         var val = parseFloat(String(s).replace(/,/g, ""));
31278         return isNaN(val) ? 0 : val;
31279     },
31280
31281     /**
31282      * Integer sorting
31283      * @param {Object} s The value being converted
31284      * @return {Number} The comparison value
31285      */
31286     asInt : function(s) {
31287         var val = parseInt(String(s).replace(/,/g, ""), 10);
31288         return isNaN(val) ? 0 : val;
31289     }
31290 });
31291 /**
31292  * Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
31293  * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the
31294  * context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching
31295  * on their records. Example usage:
31296  *
31297  *     //set up a fictional MixedCollection containing a few people to filter on
31298  *     var allNames = new Ext.util.MixedCollection();
31299  *     allNames.addAll([
31300  *         {id: 1, name: 'Ed',    age: 25},
31301  *         {id: 2, name: 'Jamie', age: 37},
31302  *         {id: 3, name: 'Abe',   age: 32},
31303  *         {id: 4, name: 'Aaron', age: 26},
31304  *         {id: 5, name: 'David', age: 32}
31305  *     ]);
31306  *
31307  *     var ageFilter = new Ext.util.Filter({
31308  *         property: 'age',
31309  *         value   : 32
31310  *     });
31311  *
31312  *     var longNameFilter = new Ext.util.Filter({
31313  *         filterFn: function(item) {
31314  *             return item.name.length > 4;
31315  *         }
31316  *     });
31317  *
31318  *     //a new MixedCollection with the 3 names longer than 4 characters
31319  *     var longNames = allNames.filter(longNameFilter);
31320  *
31321  *     //a new MixedCollection with the 2 people of age 24:
31322  *     var youngFolk = allNames.filter(ageFilter);
31323  *
31324  */
31325 Ext.define('Ext.util.Filter', {
31326
31327     /* Begin Definitions */
31328
31329     /* End Definitions */
31330     /**
31331      * @cfg {String} property
31332      * The property to filter on. Required unless a {@link #filterFn} is passed
31333      */
31334     
31335     /**
31336      * @cfg {Function} filterFn
31337      * A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should return
31338      * true to accept each item or false to reject it
31339      */
31340     
31341     /**
31342      * @cfg {Boolean} anyMatch
31343      * True to allow any match - no regex start/end line anchors will be added.
31344      */
31345     anyMatch: false,
31346     
31347     /**
31348      * @cfg {Boolean} exactMatch
31349      * True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true.
31350      */
31351     exactMatch: false,
31352     
31353     /**
31354      * @cfg {Boolean} caseSensitive
31355      * True to make the regex case sensitive (adds 'i' switch to regex).
31356      */
31357     caseSensitive: false,
31358     
31359     /**
31360      * @cfg {String} root
31361      * Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data' to
31362      * make the filter pull the {@link #property} out of the data object of each item
31363      */
31364
31365     /**
31366      * Creates new Filter.
31367      * @param {Object} [config] Config object
31368      */
31369     constructor: function(config) {
31370         var me = this;
31371         Ext.apply(me, config);
31372         
31373         //we're aliasing filter to filterFn mostly for API cleanliness reasons, despite the fact it dirties the code here.
31374         //Ext.util.Sorter takes a sorterFn property but allows .sort to be called - we do the same here
31375         me.filter = me.filter || me.filterFn;
31376         
31377         if (me.filter === undefined) {
31378             if (me.property === undefined || me.value === undefined) {
31379                 // Commented this out temporarily because it stops us using string ids in models. TODO: Remove this once
31380                 // Model has been updated to allow string ids
31381                 
31382                 // Ext.Error.raise("A Filter requires either a property or a filterFn to be set");
31383             } else {
31384                 me.filter = me.createFilterFn();
31385             }
31386             
31387             me.filterFn = me.filter;
31388         }
31389     },
31390     
31391     /**
31392      * @private
31393      * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
31394      */
31395     createFilterFn: function() {
31396         var me       = this,
31397             matcher  = me.createValueMatcher(),
31398             property = me.property;
31399         
31400         return function(item) {
31401             var value = me.getRoot.call(me, item)[property];
31402             return matcher === null ? value === null : matcher.test(value);
31403         };
31404     },
31405     
31406     /**
31407      * @private
31408      * Returns the root property of the given item, based on the configured {@link #root} property
31409      * @param {Object} item The item
31410      * @return {Object} The root property of the object
31411      */
31412     getRoot: function(item) {
31413         var root = this.root;
31414         return root === undefined ? item : item[root];
31415     },
31416     
31417     /**
31418      * @private
31419      * Returns a regular expression based on the given value and matching options
31420      */
31421     createValueMatcher : function() {
31422         var me            = this,
31423             value         = me.value,
31424             anyMatch      = me.anyMatch,
31425             exactMatch    = me.exactMatch,
31426             caseSensitive = me.caseSensitive,
31427             escapeRe      = Ext.String.escapeRegex;
31428             
31429         if (value === null) {
31430             return value;
31431         }
31432         
31433         if (!value.exec) { // not a regex
31434             value = String(value);
31435
31436             if (anyMatch === true) {
31437                 value = escapeRe(value);
31438             } else {
31439                 value = '^' + escapeRe(value);
31440                 if (exactMatch === true) {
31441                     value += '$';
31442                 }
31443             }
31444             value = new RegExp(value, caseSensitive ? '' : 'i');
31445          }
31446          
31447          return value;
31448     }
31449 });
31450 /**
31451  * Represents a single sorter that can be applied to a Store. The sorter is used
31452  * to compare two values against each other for the purpose of ordering them. Ordering
31453  * is achieved by specifying either:
31454  *
31455  * - {@link #property A sorting property}
31456  * - {@link #sorterFn A sorting function}
31457  *
31458  * As a contrived example, we can specify a custom sorter that sorts by rank:
31459  *
31460  *     Ext.define('Person', {
31461  *         extend: 'Ext.data.Model',
31462  *         fields: ['name', 'rank']
31463  *     });
31464  *
31465  *     Ext.create('Ext.data.Store', {
31466  *         model: 'Person',
31467  *         proxy: 'memory',
31468  *         sorters: [{
31469  *             sorterFn: function(o1, o2){
31470  *                 var getRank = function(o){
31471  *                     var name = o.get('rank');
31472  *                     if (name === 'first') {
31473  *                         return 1;
31474  *                     } else if (name === 'second') {
31475  *                         return 2;
31476  *                     } else {
31477  *                         return 3;
31478  *                     }
31479  *                 },
31480  *                 rank1 = getRank(o1),
31481  *                 rank2 = getRank(o2);
31482  *
31483  *                 if (rank1 === rank2) {
31484  *                     return 0;
31485  *                 }
31486  *
31487  *                 return rank1 < rank2 ? -1 : 1;
31488  *             }
31489  *         }],
31490  *         data: [{
31491  *             name: 'Person1',
31492  *             rank: 'second'
31493  *         }, {
31494  *             name: 'Person2',
31495  *             rank: 'third'
31496  *         }, {
31497  *             name: 'Person3',
31498  *             rank: 'first'
31499  *         }]
31500  *     });
31501  */
31502 Ext.define('Ext.util.Sorter', {
31503
31504     /**
31505      * @cfg {String} property
31506      * The property to sort by. Required unless {@link #sorterFn} is provided. The property is extracted from the object
31507      * directly and compared for sorting using the built in comparison operators.
31508      */
31509     
31510     /**
31511      * @cfg {Function} sorterFn
31512      * A specific sorter function to execute. Can be passed instead of {@link #property}. This sorter function allows
31513      * for any kind of custom/complex comparisons. The sorterFn receives two arguments, the objects being compared. The
31514      * function should return:
31515      *
31516      *   - -1 if o1 is "less than" o2
31517      *   - 0 if o1 is "equal" to o2
31518      *   - 1 if o1 is "greater than" o2
31519      */
31520     
31521     /**
31522      * @cfg {String} root
31523      * Optional root property. This is mostly useful when sorting a Store, in which case we set the root to 'data' to
31524      * make the filter pull the {@link #property} out of the data object of each item
31525      */
31526     
31527     /**
31528      * @cfg {Function} transform
31529      * A function that will be run on each value before it is compared in the sorter. The function will receive a single
31530      * argument, the value.
31531      */
31532     
31533     /**
31534      * @cfg {String} direction
31535      * The direction to sort by.
31536      */
31537     direction: "ASC",
31538     
31539     constructor: function(config) {
31540         var me = this;
31541         
31542         Ext.apply(me, config);
31543         
31544         if (me.property === undefined && me.sorterFn === undefined) {
31545             Ext.Error.raise("A Sorter requires either a property or a sorter function");
31546         }
31547         
31548         me.updateSortFunction();
31549     },
31550     
31551     /**
31552      * @private
31553      * Creates and returns a function which sorts an array by the given property and direction
31554      * @return {Function} A function which sorts by the property/direction combination provided
31555      */
31556     createSortFunction: function(sorterFn) {
31557         var me        = this,
31558             property  = me.property,
31559             direction = me.direction || "ASC",
31560             modifier  = direction.toUpperCase() == "DESC" ? -1 : 1;
31561         
31562         //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
31563         //-1 if object 2 is greater or 0 if they are equal
31564         return function(o1, o2) {
31565             return modifier * sorterFn.call(me, o1, o2);
31566         };
31567     },
31568     
31569     /**
31570      * @private
31571      * Basic default sorter function that just compares the defined property of each object
31572      */
31573     defaultSorterFn: function(o1, o2) {
31574         var me = this,
31575             transform = me.transform,
31576             v1 = me.getRoot(o1)[me.property],
31577             v2 = me.getRoot(o2)[me.property];
31578             
31579         if (transform) {
31580             v1 = transform(v1);
31581             v2 = transform(v2);
31582         }
31583
31584         return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
31585     },
31586     
31587     /**
31588      * @private
31589      * Returns the root property of the given item, based on the configured {@link #root} property
31590      * @param {Object} item The item
31591      * @return {Object} The root property of the object
31592      */
31593     getRoot: function(item) {
31594         return this.root === undefined ? item : item[this.root];
31595     },
31596     
31597     /**
31598      * Set the sorting direction for this sorter.
31599      * @param {String} direction The direction to sort in. Should be either 'ASC' or 'DESC'.
31600      */
31601     setDirection: function(direction) {
31602         var me = this;
31603         me.direction = direction;
31604         me.updateSortFunction();
31605     },
31606     
31607     /**
31608      * Toggles the sorting direction for this sorter.
31609      */
31610     toggle: function() {
31611         var me = this;
31612         me.direction = Ext.String.toggle(me.direction, "ASC", "DESC");
31613         me.updateSortFunction();
31614     },
31615     
31616     /**
31617      * Update the sort function for this sorter.
31618      * @param {Function} [fn] A new sorter function for this sorter. If not specified it will use the default
31619      * sorting function.
31620      */
31621     updateSortFunction: function(fn) {
31622         var me = this;
31623         fn = fn || me.sorterFn || me.defaultSorterFn;
31624         me.sort = me.createSortFunction(fn);
31625     }
31626 });
31627 /**
31628  * @author Ed Spencer
31629  *
31630  * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
31631  * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
31632  * Operation objects directly.
31633  *
31634  * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
31635  */
31636 Ext.define('Ext.data.Operation', {
31637     /**
31638      * @cfg {Boolean} synchronous
31639      * True if this Operation is to be executed synchronously. This property is inspected by a
31640      * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
31641      */
31642     synchronous: true,
31643
31644     /**
31645      * @cfg {String} action
31646      * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
31647      */
31648     action: undefined,
31649
31650     /**
31651      * @cfg {Ext.util.Filter[]} filters
31652      * Optional array of filter objects. Only applies to 'read' actions.
31653      */
31654     filters: undefined,
31655
31656     /**
31657      * @cfg {Ext.util.Sorter[]} sorters
31658      * Optional array of sorter objects. Only applies to 'read' actions.
31659      */
31660     sorters: undefined,
31661
31662     /**
31663      * @cfg {Ext.util.Grouper} group
31664      * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
31665      */
31666     group: undefined,
31667
31668     /**
31669      * @cfg {Number} start
31670      * The start index (offset), used in paging when running a 'read' action.
31671      */
31672     start: undefined,
31673
31674     /**
31675      * @cfg {Number} limit
31676      * The number of records to load. Used on 'read' actions when paging is being used.
31677      */
31678     limit: undefined,
31679
31680     /**
31681      * @cfg {Ext.data.Batch} batch
31682      * The batch that this Operation is a part of.
31683      */
31684     batch: undefined,
31685
31686     /**
31687      * @cfg {Function} callback
31688      * Function to execute when operation completed.  Will be called with the following parameters:
31689      *
31690      * - records : Array of Ext.data.Model objects.
31691      * - operation : The Ext.data.Operation itself.
31692      * - success : True when operation completed successfully.
31693      */
31694     callback: undefined,
31695
31696     /**
31697      * @cfg {Object} scope
31698      * Scope for the {@link #callback} function.
31699      */
31700     scope: undefined,
31701
31702     /**
31703      * @property {Boolean} started
31704      * Read-only property tracking the start status of this Operation. Use {@link #isStarted}.
31705      * @private
31706      */
31707     started: false,
31708
31709     /**
31710      * @property {Boolean} running
31711      * Read-only property tracking the run status of this Operation. Use {@link #isRunning}.
31712      * @private
31713      */
31714     running: false,
31715
31716     /**
31717      * @property {Boolean} complete
31718      * Read-only property tracking the completion status of this Operation. Use {@link #isComplete}.
31719      * @private
31720      */
31721     complete: false,
31722
31723     /**
31724      * @property {Boolean} success
31725      * Read-only property tracking whether the Operation was successful or not. This starts as undefined and is set to true
31726      * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
31727      * {@link #wasSuccessful} to query success status.
31728      * @private
31729      */
31730     success: undefined,
31731
31732     /**
31733      * @property {Boolean} exception
31734      * Read-only property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
31735      * @private
31736      */
31737     exception: false,
31738
31739     /**
31740      * @property {String/Object} error
31741      * The error object passed when {@link #setException} was called. This could be any object or primitive.
31742      * @private
31743      */
31744     error: undefined,
31745
31746     /**
31747      * @property {RegExp} actionCommitRecordsRe
31748      * The RegExp used to categorize actions that require record commits.
31749      */
31750     actionCommitRecordsRe: /^(?:create|update)$/i,
31751
31752     /**
31753      * @property {RegExp} actionSkipSyncRe
31754      * The RegExp used to categorize actions that skip local record synchronization. This defaults
31755      * to match 'destroy'.
31756      */
31757     actionSkipSyncRe: /^destroy$/i,
31758
31759     /**
31760      * Creates new Operation object.
31761      * @param {Object} config (optional) Config object.
31762      */
31763     constructor: function(config) {
31764         Ext.apply(this, config || {});
31765     },
31766
31767     /**
31768      * This method is called to commit data to this instance's records given the records in
31769      * the server response. This is followed by calling {@link Ext.data.Model#commit} on all
31770      * those records (for 'create' and 'update' actions).
31771      *
31772      * If this {@link #action} is 'destroy', any server records are ignored and the
31773      * {@link Ext.data.Model#commit} method is not called.
31774      *
31775      * @param {Ext.data.Model[]} serverRecords An array of {@link Ext.data.Model} objects returned by
31776      * the server.
31777      * @markdown
31778      */
31779     commitRecords: function (serverRecords) {
31780         var me = this,
31781             mc, index, clientRecords, serverRec, clientRec;
31782
31783         if (!me.actionSkipSyncRe.test(me.action)) {
31784             clientRecords = me.records;
31785
31786             if (clientRecords && clientRecords.length) {
31787                 mc = Ext.create('Ext.util.MixedCollection', true, function(r) {return r.getId();});
31788                 mc.addAll(clientRecords);
31789
31790                 for (index = serverRecords ? serverRecords.length : 0; index--; ) {
31791                     serverRec = serverRecords[index];
31792                     clientRec = mc.get(serverRec.getId());
31793
31794                     if (clientRec) {
31795                         clientRec.beginEdit();
31796                         clientRec.set(serverRec.data);
31797                         clientRec.endEdit(true);
31798                     }
31799                 }
31800
31801                 if (me.actionCommitRecordsRe.test(me.action)) {
31802                     for (index = clientRecords.length; index--; ) {
31803                         clientRecords[index].commit();
31804                     }
31805                 }
31806             }
31807         }
31808     },
31809
31810     /**
31811      * Marks the Operation as started.
31812      */
31813     setStarted: function() {
31814         this.started = true;
31815         this.running = true;
31816     },
31817
31818     /**
31819      * Marks the Operation as completed.
31820      */
31821     setCompleted: function() {
31822         this.complete = true;
31823         this.running  = false;
31824     },
31825
31826     /**
31827      * Marks the Operation as successful.
31828      */
31829     setSuccessful: function() {
31830         this.success = true;
31831     },
31832
31833     /**
31834      * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
31835      * @param {String/Object} error (optional) error string/object
31836      */
31837     setException: function(error) {
31838         this.exception = true;
31839         this.success = false;
31840         this.running = false;
31841         this.error = error;
31842     },
31843
31844     /**
31845      * Returns true if this Operation encountered an exception (see also {@link #getError})
31846      * @return {Boolean} True if there was an exception
31847      */
31848     hasException: function() {
31849         return this.exception === true;
31850     },
31851
31852     /**
31853      * Returns the error string or object that was set using {@link #setException}
31854      * @return {String/Object} The error object
31855      */
31856     getError: function() {
31857         return this.error;
31858     },
31859
31860     /**
31861      * Returns an array of Ext.data.Model instances as set by the Proxy.
31862      * @return {Ext.data.Model[]} Any loaded Records
31863      */
31864     getRecords: function() {
31865         var resultSet = this.getResultSet();
31866
31867         return (resultSet === undefined ? this.records : resultSet.records);
31868     },
31869
31870     /**
31871      * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model}
31872      * instances as well as meta data such as number of instances fetched, number available etc
31873      * @return {Ext.data.ResultSet} The ResultSet object
31874      */
31875     getResultSet: function() {
31876         return this.resultSet;
31877     },
31878
31879     /**
31880      * Returns true if the Operation has been started. Note that the Operation may have started AND completed, see
31881      * {@link #isRunning} to test if the Operation is currently running.
31882      * @return {Boolean} True if the Operation has started
31883      */
31884     isStarted: function() {
31885         return this.started === true;
31886     },
31887
31888     /**
31889      * Returns true if the Operation has been started but has not yet completed.
31890      * @return {Boolean} True if the Operation is currently running
31891      */
31892     isRunning: function() {
31893         return this.running === true;
31894     },
31895
31896     /**
31897      * Returns true if the Operation has been completed
31898      * @return {Boolean} True if the Operation is complete
31899      */
31900     isComplete: function() {
31901         return this.complete === true;
31902     },
31903
31904     /**
31905      * Returns true if the Operation has completed and was successful
31906      * @return {Boolean} True if successful
31907      */
31908     wasSuccessful: function() {
31909         return this.isComplete() && this.success === true;
31910     },
31911
31912     /**
31913      * @private
31914      * Associates this Operation with a Batch
31915      * @param {Ext.data.Batch} batch The batch
31916      */
31917     setBatch: function(batch) {
31918         this.batch = batch;
31919     },
31920
31921     /**
31922      * Checks whether this operation should cause writing to occur.
31923      * @return {Boolean} Whether the operation should cause a write to occur.
31924      */
31925     allowWrite: function() {
31926         return this.action != 'read';
31927     }
31928 });
31929 /**
31930  * @author Ed Spencer
31931  *
31932  * This singleton contains a set of validation functions that can be used to validate any type of data. They are most
31933  * often used in {@link Ext.data.Model Models}, where they are automatically set up and executed.
31934  */
31935 Ext.define('Ext.data.validations', {
31936     singleton: true,
31937     
31938     /**
31939      * @property {String} presenceMessage
31940      * The default error message used when a presence validation fails.
31941      */
31942     presenceMessage: 'must be present',
31943     
31944     /**
31945      * @property {String} lengthMessage
31946      * The default error message used when a length validation fails.
31947      */
31948     lengthMessage: 'is the wrong length',
31949     
31950     /**
31951      * @property {Boolean} formatMessage
31952      * The default error message used when a format validation fails.
31953      */
31954     formatMessage: 'is the wrong format',
31955     
31956     /**
31957      * @property {String} inclusionMessage
31958      * The default error message used when an inclusion validation fails.
31959      */
31960     inclusionMessage: 'is not included in the list of acceptable values',
31961     
31962     /**
31963      * @property {String} exclusionMessage
31964      * The default error message used when an exclusion validation fails.
31965      */
31966     exclusionMessage: 'is not an acceptable value',
31967     
31968     /**
31969      * @property {String} emailMessage
31970      * The default error message used when an email validation fails
31971      */
31972     emailMessage: 'is not a valid email address',
31973     
31974     /**
31975      * @property {RegExp} emailRe
31976      * The regular expression used to validate email addresses
31977      */
31978     emailRe: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
31979     
31980     /**
31981      * Validates that the given value is present.
31982      * For example:
31983      *
31984      *     validations: [{type: 'presence', field: 'age'}]
31985      *
31986      * @param {Object} config Config object
31987      * @param {Object} value The value to validate
31988      * @return {Boolean} True if validation passed
31989      */
31990     presence: function(config, value) {
31991         if (value === undefined) {
31992             value = config;
31993         }
31994         
31995         //we need an additional check for zero here because zero is an acceptable form of present data
31996         return !!value || value === 0;
31997     },
31998     
31999     /**
32000      * Returns true if the given value is between the configured min and max values.
32001      * For example:
32002      *
32003      *     validations: [{type: 'length', field: 'name', min: 2}]
32004      *
32005      * @param {Object} config Config object
32006      * @param {String} value The value to validate
32007      * @return {Boolean} True if the value passes validation
32008      */
32009     length: function(config, value) {
32010         if (value === undefined || value === null) {
32011             return false;
32012         }
32013         
32014         var length = value.length,
32015             min    = config.min,
32016             max    = config.max;
32017         
32018         if ((min && length < min) || (max && length > max)) {
32019             return false;
32020         } else {
32021             return true;
32022         }
32023     },
32024     
32025     /**
32026      * Validates that an email string is in the correct format
32027      * @param {Object} config Config object
32028      * @param {String} email The email address
32029      * @return {Boolean} True if the value passes validation
32030      */
32031     email: function(config, email) {
32032         return Ext.data.validations.emailRe.test(email);
32033     },
32034     
32035     /**
32036      * Returns true if the given value passes validation against the configured `matcher` regex.
32037      * For example:
32038      *
32039      *     validations: [{type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}]
32040      *
32041      * @param {Object} config Config object
32042      * @param {String} value The value to validate
32043      * @return {Boolean} True if the value passes the format validation
32044      */
32045     format: function(config, value) {
32046         return !!(config.matcher && config.matcher.test(value));
32047     },
32048     
32049     /**
32050      * Validates that the given value is present in the configured `list`.
32051      * For example:
32052      *
32053      *     validations: [{type: 'inclusion', field: 'gender', list: ['Male', 'Female']}]
32054      *
32055      * @param {Object} config Config object
32056      * @param {String} value The value to validate
32057      * @return {Boolean} True if the value is present in the list
32058      */
32059     inclusion: function(config, value) {
32060         return config.list && Ext.Array.indexOf(config.list,value) != -1;
32061     },
32062     
32063     /**
32064      * Validates that the given value is present in the configured `list`.
32065      * For example:
32066      *
32067      *     validations: [{type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}]
32068      *
32069      * @param {Object} config Config object
32070      * @param {String} value The value to validate
32071      * @return {Boolean} True if the value is not present in the list
32072      */
32073     exclusion: function(config, value) {
32074         return config.list && Ext.Array.indexOf(config.list,value) == -1;
32075     }
32076 });
32077 /**
32078  * @author Ed Spencer
32079  *
32080  * Simple wrapper class that represents a set of records returned by a Proxy.
32081  */
32082 Ext.define('Ext.data.ResultSet', {
32083     /**
32084      * @cfg {Boolean} loaded
32085      * True if the records have already been loaded. This is only meaningful when dealing with
32086      * SQL-backed proxies.
32087      */
32088     loaded: true,
32089
32090     /**
32091      * @cfg {Number} count
32092      * The number of records in this ResultSet. Note that total may differ from this number.
32093      */
32094     count: 0,
32095
32096     /**
32097      * @cfg {Number} total
32098      * The total number of records reported by the data source. This ResultSet may form a subset of
32099      * those records (see {@link #count}).
32100      */
32101     total: 0,
32102
32103     /**
32104      * @cfg {Boolean} success
32105      * True if the ResultSet loaded successfully, false if any errors were encountered.
32106      */
32107     success: false,
32108
32109     /**
32110      * @cfg {Ext.data.Model[]} records (required)
32111      * The array of record instances.
32112      */
32113
32114     /**
32115      * Creates the resultSet
32116      * @param {Object} [config] Config object.
32117      */
32118     constructor: function(config) {
32119         Ext.apply(this, config);
32120
32121         /**
32122          * @property {Number} totalRecords
32123          * Copy of this.total.
32124          * @deprecated Will be removed in Ext JS 5.0. Use {@link #total} instead.
32125          */
32126         this.totalRecords = this.total;
32127
32128         if (config.count === undefined) {
32129             this.count = this.records.length;
32130         }
32131     }
32132 });
32133 /**
32134  * @author Ed Spencer
32135  *
32136  * Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is responsible for taking a
32137  * set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request} object and modifying that request based on
32138  * the Operations.
32139  *
32140  * For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model} instances based on
32141  * the config options passed to the JsonWriter's constructor.
32142  *
32143  * Writers are not needed for any kind of local storage - whether via a {@link Ext.data.proxy.WebStorage Web Storage
32144  * proxy} (see {@link Ext.data.proxy.LocalStorage localStorage} and {@link Ext.data.proxy.SessionStorage
32145  * sessionStorage}) or just in memory via a {@link Ext.data.proxy.Memory MemoryProxy}.
32146  */
32147 Ext.define('Ext.data.writer.Writer', {
32148     alias: 'writer.base',
32149     alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
32150     
32151     /**
32152      * @cfg {Boolean} writeAllFields
32153      * True to write all fields from the record to the server. If set to false it will only send the fields that were
32154      * modified. Note that any fields that have {@link Ext.data.Field#persist} set to false will still be ignored.
32155      */
32156     writeAllFields: true,
32157     
32158     /**
32159      * @cfg {String} nameProperty
32160      * This property is used to read the key for each value that will be sent to the server. For example:
32161      *
32162      *     Ext.define('Person', {
32163      *         extend: 'Ext.data.Model',
32164      *         fields: [{
32165      *             name: 'first',
32166      *             mapping: 'firstName'
32167      *         }, {
32168      *             name: 'last',
32169      *             mapping: 'lastName'
32170      *         }, {
32171      *             name: 'age'
32172      *         }]
32173      *     });
32174      *     new Ext.data.writer.Writer({
32175      *         writeAllFields: true,
32176      *         nameProperty: 'mapping'
32177      *     });
32178      *
32179      *     // This will be sent to the server
32180      *     {
32181      *         firstName: 'first name value',
32182      *         lastName: 'last name value',
32183      *         age: 1
32184      *     }
32185      *
32186      * If the value is not present, the field name will always be used.
32187      */
32188     nameProperty: 'name',
32189
32190     /**
32191      * Creates new Writer.
32192      * @param {Object} [config] Config object.
32193      */
32194     constructor: function(config) {
32195         Ext.apply(this, config);
32196     },
32197
32198     /**
32199      * Prepares a Proxy's Ext.data.Request object
32200      * @param {Ext.data.Request} request The request object
32201      * @return {Ext.data.Request} The modified request object
32202      */
32203     write: function(request) {
32204         var operation = request.operation,
32205             records   = operation.records || [],
32206             len       = records.length,
32207             i         = 0,
32208             data      = [];
32209
32210         for (; i < len; i++) {
32211             data.push(this.getRecordData(records[i]));
32212         }
32213         return this.writeRecords(request, data);
32214     },
32215
32216     /**
32217      * Formats the data for each record before sending it to the server. This method should be overridden to format the
32218      * data in a way that differs from the default.
32219      * @param {Object} record The record that we are writing to the server.
32220      * @return {Object} An object literal of name/value keys to be written to the server. By default this method returns
32221      * the data property on the record.
32222      */
32223     getRecordData: function(record) {
32224         var isPhantom = record.phantom === true,
32225             writeAll = this.writeAllFields || isPhantom,
32226             nameProperty = this.nameProperty,
32227             fields = record.fields,
32228             data = {},
32229             changes,
32230             name,
32231             field,
32232             key;
32233         
32234         if (writeAll) {
32235             fields.each(function(field){
32236                 if (field.persist) {
32237                     name = field[nameProperty] || field.name;
32238                     data[name] = record.get(field.name);
32239                 }
32240             });
32241         } else {
32242             // Only write the changes
32243             changes = record.getChanges();
32244             for (key in changes) {
32245                 if (changes.hasOwnProperty(key)) {
32246                     field = fields.get(key);
32247                     name = field[nameProperty] || field.name;
32248                     data[name] = changes[key];
32249                 }
32250             }
32251             if (!isPhantom) {
32252                 // always include the id for non phantoms
32253                 data[record.idProperty] = record.getId();
32254             }
32255         }
32256         return data;
32257     }
32258 });
32259
32260 /**
32261  * A mixin to add floating capability to a Component.
32262  */
32263 Ext.define('Ext.util.Floating', {
32264
32265     uses: ['Ext.Layer', 'Ext.window.Window'],
32266
32267     /**
32268      * @cfg {Boolean} focusOnToFront
32269      * Specifies whether the floated component should be automatically {@link Ext.Component#focus focused} when
32270      * it is {@link #toFront brought to the front}.
32271      */
32272     focusOnToFront: true,
32273
32274     /**
32275      * @cfg {String/Boolean} shadow
32276      * Specifies whether the floating component should be given a shadow. Set to true to automatically create an {@link
32277      * Ext.Shadow}, or a string indicating the shadow's display {@link Ext.Shadow#mode}. Set to false to disable the
32278      * shadow.
32279      */
32280     shadow: 'sides',
32281
32282     constructor: function(config) {
32283         var me = this;
32284         
32285         me.floating = true;
32286         me.el = Ext.create('Ext.Layer', Ext.apply({}, config, {
32287             hideMode: me.hideMode,
32288             hidden: me.hidden,
32289             shadow: Ext.isDefined(me.shadow) ? me.shadow : 'sides',
32290             shadowOffset: me.shadowOffset,
32291             constrain: false,
32292             shim: me.shim === false ? false : undefined
32293         }), me.el);
32294     },
32295
32296     onFloatRender: function() {
32297         var me = this;
32298         me.zIndexParent = me.getZIndexParent();
32299         me.setFloatParent(me.ownerCt);
32300         delete me.ownerCt;
32301
32302         if (me.zIndexParent) {
32303             me.zIndexParent.registerFloatingItem(me);
32304         } else {
32305             Ext.WindowManager.register(me);
32306         }
32307     },
32308
32309     setFloatParent: function(floatParent) {
32310         var me = this;
32311
32312         // Remove listeners from previous floatParent
32313         if (me.floatParent) {
32314             me.mun(me.floatParent, {
32315                 hide: me.onFloatParentHide,
32316                 show: me.onFloatParentShow,
32317                 scope: me
32318             });
32319         }
32320
32321         me.floatParent = floatParent;
32322
32323         // Floating Components as children of Containers must hide when their parent hides.
32324         if (floatParent) {
32325             me.mon(me.floatParent, {
32326                 hide: me.onFloatParentHide,
32327                 show: me.onFloatParentShow,
32328                 scope: me
32329             });
32330         }
32331
32332         // If a floating Component is configured to be constrained, but has no configured
32333         // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
32334         if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
32335             me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
32336         }
32337     },
32338
32339     onFloatParentHide: function() {
32340         var me = this;
32341         
32342         if (me.hideOnParentHide !== false) {
32343             me.showOnParentShow = me.isVisible();
32344             me.hide();
32345         }
32346     },
32347
32348     onFloatParentShow: function() {
32349         if (this.showOnParentShow) {
32350             delete this.showOnParentShow;
32351             this.show();
32352         }
32353     },
32354
32355     /**
32356      * @private
32357      * Finds the ancestor Container responsible for allocating zIndexes for the passed Component.
32358      *
32359      * That will be the outermost floating Container (a Container which has no ownerCt and has floating:true).
32360      *
32361      * If we have no ancestors, or we walk all the way up to the document body, there's no zIndexParent,
32362      * and the global Ext.WindowManager will be used.
32363      */
32364     getZIndexParent: function() {
32365         var p = this.ownerCt,
32366             c;
32367
32368         if (p) {
32369             while (p) {
32370                 c = p;
32371                 p = p.ownerCt;
32372             }
32373             if (c.floating) {
32374                 return c;
32375             }
32376         }
32377     },
32378
32379     // private
32380     // z-index is managed by the zIndexManager and may be overwritten at any time.
32381     // Returns the next z-index to be used.
32382     // If this is a Container, then it will have rebased any managed floating Components,
32383     // and so the next available z-index will be approximately 10000 above that.
32384     setZIndex: function(index) {
32385         var me = this;
32386         me.el.setZIndex(index);
32387
32388         // Next item goes 10 above;
32389         index += 10;
32390
32391         // When a Container with floating items has its z-index set, it rebases any floating items it is managing.
32392         // The returned value is a round number approximately 10000 above the last z-index used.
32393         if (me.floatingItems) {
32394             index = Math.floor(me.floatingItems.setBase(index) / 100) * 100 + 10000;
32395         }
32396         return index;
32397     },
32398
32399     /**
32400      * Moves this floating Component into a constrain region.
32401      *
32402      * By default, this Component is constrained to be within the container it was added to, or the element it was
32403      * rendered to.
32404      *
32405      * An alternative constraint may be passed.
32406      * @param {String/HTMLElement/Ext.Element/Ext.util.Region} constrainTo (Optional) The Element or {@link Ext.util.Region Region} into which this Component is
32407      * to be constrained. Defaults to the element into which this floating Component was rendered.
32408      */
32409     doConstrain: function(constrainTo) {
32410         var me = this,
32411             vector = me.getConstrainVector(constrainTo || me.el.getScopeParent()),
32412             xy;
32413
32414         if (vector) {
32415             xy = me.getPosition();
32416             xy[0] += vector[0];
32417             xy[1] += vector[1];
32418             me.setPosition(xy);
32419         }
32420     },
32421
32422
32423     /**
32424      * Gets the x/y offsets to constrain this float
32425      * @private
32426      * @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.
32427      * @return {Number[]} The x/y constraints
32428      */
32429     getConstrainVector: function(constrainTo){
32430         var me = this,
32431             el;
32432
32433         if (me.constrain || me.constrainHeader) {
32434             el = me.constrainHeader ? me.header.el : me.el;
32435             constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container;
32436             return el.getConstrainVector(constrainTo);
32437         }
32438     },
32439
32440     /**
32441      * Aligns this floating Component to the specified element
32442      *
32443      * @param {Ext.Component/Ext.Element/HTMLElement/String} element
32444      * The element or {@link Ext.Component} to align to. If passing a component, it must be a
32445      * omponent instance. If a string id is passed, it will be used as an element id.
32446      * @param {String} [position="tl-bl?"] The position to align to (see {@link
32447      * Ext.Element#alignTo} for more details).
32448      * @param {Number[]} [offsets] Offset the positioning by [x, y]
32449      * @return {Ext.Component} this
32450      */
32451     alignTo: function(element, position, offsets) {
32452         if (element.isComponent) {
32453             element = element.getEl();
32454         }
32455         var xy = this.el.getAlignToXY(element, position, offsets);
32456         this.setPagePosition(xy);
32457         return this;
32458     },
32459
32460     /**
32461      * Brings this floating Component to the front of any other visible, floating Components managed by the same {@link
32462      * Ext.ZIndexManager ZIndexManager}
32463      *
32464      * If this Component is modal, inserts the modal mask just below this Component in the z-index stack.
32465      *
32466      * @param {Boolean} [preventFocus=false] Specify `true` to prevent the Component from being focused.
32467      * @return {Ext.Component} this
32468      */
32469     toFront: function(preventFocus) {
32470         var me = this;
32471
32472         // Find the floating Component which provides the base for this Component's zIndexing.
32473         // That must move to front to then be able to rebase its zIndex stack and move this to the front
32474         if (me.zIndexParent) {
32475             me.zIndexParent.toFront(true);
32476         }
32477         if (me.zIndexManager.bringToFront(me)) {
32478             if (!Ext.isDefined(preventFocus)) {
32479                 preventFocus = !me.focusOnToFront;
32480             }
32481             if (!preventFocus) {
32482                 // Kick off a delayed focus request.
32483                 // If another floating Component is toFronted before the delay expires
32484                 // this will not receive focus.
32485                 me.focus(false, true);
32486             }
32487         }
32488         return me;
32489     },
32490
32491     /**
32492      * This method is called internally by {@link Ext.ZIndexManager} to signal that a floating Component has either been
32493      * moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.
32494      *
32495      * If a _Window_ is superceded by another Window, deactivating it hides its shadow.
32496      *
32497      * This method also fires the {@link Ext.Component#activate activate} or
32498      * {@link Ext.Component#deactivate deactivate} event depending on which action occurred.
32499      *
32500      * @param {Boolean} [active=false] True to activate the Component, false to deactivate it.
32501      * @param {Ext.Component} [newActive] The newly active Component which is taking over topmost zIndex position.
32502      */
32503     setActive: function(active, newActive) {
32504         var me = this;
32505         
32506         if (active) {
32507             if (me.el.shadow && !me.maximized) {
32508                 me.el.enableShadow(true);
32509             }
32510             me.fireEvent('activate', me);
32511         } else {
32512             // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
32513             // can keep their shadows all the time
32514             if ((me instanceof Ext.window.Window) && (newActive instanceof Ext.window.Window)) {
32515                 me.el.disableShadow();
32516             }
32517             me.fireEvent('deactivate', me);
32518         }
32519     },
32520
32521     /**
32522      * Sends this Component to the back of (lower z-index than) any other visible windows
32523      * @return {Ext.Component} this
32524      */
32525     toBack: function() {
32526         this.zIndexManager.sendToBack(this);
32527         return this;
32528     },
32529
32530     /**
32531      * Center this Component in its container.
32532      * @return {Ext.Component} this
32533      */
32534     center: function() {
32535         var me = this,
32536             xy = me.el.getAlignToXY(me.container, 'c-c');
32537         me.setPagePosition(xy);
32538         return me;
32539     },
32540
32541     // private
32542     syncShadow : function(){
32543         if (this.floating) {
32544             this.el.sync(true);
32545         }
32546     },
32547
32548     // private
32549     fitContainer: function() {
32550         var parent = this.floatParent,
32551             container = parent ? parent.getTargetEl() : this.container,
32552             size = container.getViewSize(false);
32553
32554         this.setSize(size);
32555     }
32556 });
32557 /**
32558  * Base Layout class - extended by ComponentLayout and ContainerLayout
32559  */
32560 Ext.define('Ext.layout.Layout', {
32561
32562     /* Begin Definitions */
32563
32564     /* End Definitions */
32565
32566     isLayout: true,
32567     initialized: false,
32568
32569     statics: {
32570         create: function(layout, defaultType) {
32571             var type;
32572             if (layout instanceof Ext.layout.Layout) {
32573                 return Ext.createByAlias('layout.' + layout);
32574             } else {
32575                 if (!layout || typeof layout === 'string') {
32576                     type = layout || defaultType;
32577                     layout = {};                    
32578                 }
32579                 else {
32580                     type = layout.type || defaultType;
32581                 }
32582                 return Ext.createByAlias('layout.' + type, layout || {});
32583             }
32584         }
32585     },
32586
32587     constructor : function(config) {
32588         this.id = Ext.id(null, this.type + '-');
32589         Ext.apply(this, config);
32590     },
32591
32592     /**
32593      * @private
32594      */
32595     layout : function() {
32596         var me = this;
32597         me.layoutBusy = true;
32598         me.initLayout();
32599
32600         if (me.beforeLayout.apply(me, arguments) !== false) {
32601             me.layoutCancelled = false;
32602             me.onLayout.apply(me, arguments);
32603             me.childrenChanged = false;
32604             me.owner.needsLayout = false;
32605             me.layoutBusy = false;
32606             me.afterLayout.apply(me, arguments);
32607         }
32608         else {
32609             me.layoutCancelled = true;
32610         }
32611         me.layoutBusy = false;
32612         me.doOwnerCtLayouts();
32613     },
32614
32615     beforeLayout : function() {
32616         this.renderChildren();
32617         return true;
32618     },
32619
32620     renderChildren: function () {
32621         this.renderItems(this.getLayoutItems(), this.getRenderTarget());
32622     },
32623
32624     /**
32625      * @private
32626      * Iterates over all passed items, ensuring they are rendered.  If the items are already rendered,
32627      * also determines if the items are in the proper place dom.
32628      */
32629     renderItems : function(items, target) {
32630         var me = this,
32631             ln = items.length,
32632             i = 0,
32633             item;
32634
32635         for (; i < ln; i++) {
32636             item = items[i];
32637             if (item && !item.rendered) {
32638                 me.renderItem(item, target, i);
32639             } else if (!me.isValidParent(item, target, i)) {
32640                 me.moveItem(item, target, i);
32641             } else {
32642                 // still need to configure the item, it may have moved in the container.
32643                 me.configureItem(item);
32644             }
32645         }
32646     },
32647
32648     // @private - Validates item is in the proper place in the dom.
32649     isValidParent : function(item, target, position) {
32650         var dom = item.el ? item.el.dom : Ext.getDom(item);
32651         if (dom && target && target.dom) {
32652             if (Ext.isNumber(position) && dom !== target.dom.childNodes[position]) {
32653                 return false;
32654             }
32655             return (dom.parentNode == (target.dom || target));
32656         }
32657         return false;
32658     },
32659
32660     /**
32661      * @private
32662      * Renders the given Component into the target Element.
32663      * @param {Ext.Component} item The Component to render
32664      * @param {Ext.Element} target The target Element
32665      * @param {Number} position The position within the target to render the item to
32666      */
32667     renderItem : function(item, target, position) {
32668         var me = this;
32669         if (!item.rendered) {
32670             if (me.itemCls) {
32671                 item.addCls(me.itemCls);
32672             }
32673             if (me.owner.itemCls) {
32674                 item.addCls(me.owner.itemCls);
32675             }
32676             item.render(target, position);
32677             me.configureItem(item);
32678             me.childrenChanged = true;
32679         }
32680     },
32681
32682     /**
32683      * @private
32684      * Moved Component to the provided target instead.
32685      */
32686     moveItem : function(item, target, position) {
32687         // Make sure target is a dom element
32688         target = target.dom || target;
32689         if (typeof position == 'number') {
32690             position = target.childNodes[position];
32691         }
32692         target.insertBefore(item.el.dom, position || null);
32693         item.container = Ext.get(target);
32694         this.configureItem(item);
32695         this.childrenChanged = true;
32696     },
32697
32698     /**
32699      * @private
32700      * Adds the layout's targetCls if necessary and sets
32701      * initialized flag when complete.
32702      */
32703     initLayout : function() {
32704         var me = this,
32705             targetCls = me.targetCls;
32706             
32707         if (!me.initialized && !Ext.isEmpty(targetCls)) {
32708             me.getTarget().addCls(targetCls);
32709         }
32710         me.initialized = true;
32711     },
32712
32713     // @private Sets the layout owner
32714     setOwner : function(owner) {
32715         this.owner = owner;
32716     },
32717
32718     // @private - Returns empty array
32719     getLayoutItems : function() {
32720         return [];
32721     },
32722
32723     /**
32724      * @private
32725      * Applies itemCls
32726      * Empty template method
32727      */
32728     configureItem: Ext.emptyFn,
32729     
32730     // Placeholder empty functions for subclasses to extend
32731     onLayout : Ext.emptyFn,
32732     afterLayout : Ext.emptyFn,
32733     onRemove : Ext.emptyFn,
32734     onDestroy : Ext.emptyFn,
32735     doOwnerCtLayouts : Ext.emptyFn,
32736
32737     /**
32738      * @private
32739      * Removes itemCls
32740      */
32741     afterRemove : function(item) {
32742         var el = item.el,
32743             owner = this.owner,
32744             itemCls = this.itemCls,
32745             ownerCls = owner.itemCls;
32746             
32747         // Clear managed dimensions flag when removed from the layout.
32748         if (item.rendered && !item.isDestroyed) {
32749             if (itemCls) {
32750                 el.removeCls(itemCls);
32751             }
32752             if (ownerCls) {
32753                 el.removeCls(ownerCls);
32754             }
32755         }
32756
32757         // These flags are set at the time a child item is added to a layout.
32758         // The layout must decide if it is managing the item's width, or its height, or both.
32759         // See AbstractComponent for docs on these properties.
32760         delete item.layoutManagedWidth;
32761         delete item.layoutManagedHeight;
32762     },
32763
32764     /**
32765      * Destroys this layout. This is a template method that is empty by default, but should be implemented
32766      * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
32767      * @template
32768      */
32769     destroy : function() {
32770         var targetCls = this.targetCls,
32771             target;
32772         
32773         if (!Ext.isEmpty(targetCls)) {
32774             target = this.getTarget();
32775             if (target) {
32776                 target.removeCls(targetCls);
32777             }
32778         }
32779         this.onDestroy();
32780     }
32781 });
32782 /**
32783  * @class Ext.ZIndexManager
32784  * <p>A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
32785  * and Component activation behavior, including masking below the active (topmost) Component.</p>
32786  * <p>{@link Ext.Component#floating Floating} Components which are rendered directly into the document (such as {@link Ext.window.Window Window}s) which are
32787  * {@link Ext.Component#show show}n are managed by a {@link Ext.WindowManager global instance}.</p>
32788  * <p>{@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating} <i>Containers</i>
32789  * (for example a {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window}, or a {@link Ext.menu.Menu Menu}),
32790  * are managed by a ZIndexManager owned by that floating Container. Therefore ComboBox dropdowns within Windows will have managed z-indices
32791  * guaranteed to be correct, relative to the Window.</p>
32792  */
32793 Ext.define('Ext.ZIndexManager', {
32794
32795     alternateClassName: 'Ext.WindowGroup',
32796
32797     statics: {
32798         zBase : 9000
32799     },
32800
32801     constructor: function(container) {
32802         var me = this;
32803
32804         me.list = {};
32805         me.zIndexStack = [];
32806         me.front = null;
32807
32808         if (container) {
32809
32810             // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
32811             if (container.isContainer) {
32812                 container.on('resize', me._onContainerResize, me);
32813                 me.zseed = Ext.Number.from(container.getEl().getStyle('zIndex'), me.getNextZSeed());
32814                 // The containing element we will be dealing with (eg masking) is the content target
32815                 me.targetEl = container.getTargetEl();
32816                 me.container = container;
32817             }
32818             // This is the ZIndexManager for a DOM element
32819             else {
32820                 Ext.EventManager.onWindowResize(me._onContainerResize, me);
32821                 me.zseed = me.getNextZSeed();
32822                 me.targetEl = Ext.get(container);
32823             }
32824         }
32825         // No container passed means we are the global WindowManager. Our target is the doc body.
32826         // DOM must be ready to collect that ref.
32827         else {
32828             Ext.EventManager.onWindowResize(me._onContainerResize, me);
32829             me.zseed = me.getNextZSeed();
32830             Ext.onDocumentReady(function() {
32831                 me.targetEl = Ext.getBody();
32832             });
32833         }
32834     },
32835
32836     getNextZSeed: function() {
32837         return (Ext.ZIndexManager.zBase += 10000);
32838     },
32839
32840     setBase: function(baseZIndex) {
32841         this.zseed = baseZIndex;
32842         return this.assignZIndices();
32843     },
32844
32845     // private
32846     assignZIndices: function() {
32847         var a = this.zIndexStack,
32848             len = a.length,
32849             i = 0,
32850             zIndex = this.zseed,
32851             comp;
32852
32853         for (; i < len; i++) {
32854             comp = a[i];
32855             if (comp && !comp.hidden) {
32856
32857                 // Setting the zIndex of a Component returns the topmost zIndex consumed by
32858                 // that Component.
32859                 // If it's just a plain floating Component such as a BoundList, then the
32860                 // return value is the passed value plus 10, ready for the next item.
32861                 // If a floating *Container* has its zIndex set, it re-orders its managed
32862                 // floating children, starting from that new base, and returns a value 10000 above
32863                 // the highest zIndex which it allocates.
32864                 zIndex = comp.setZIndex(zIndex);
32865             }
32866         }
32867         this._activateLast();
32868         return zIndex;
32869     },
32870
32871     // private
32872     _setActiveChild: function(comp) {
32873         if (comp !== this.front) {
32874
32875             if (this.front) {
32876                 this.front.setActive(false, comp);
32877             }
32878             this.front = comp;
32879             if (comp) {
32880                 comp.setActive(true);
32881                 if (comp.modal) {
32882                     this._showModalMask(comp);
32883                 }
32884             }
32885         }
32886     },
32887
32888     // private
32889     _activateLast: function(justHidden) {
32890         var comp,
32891             lastActivated = false,
32892             i;
32893
32894         // Go down through the z-index stack.
32895         // Activate the next visible one down.
32896         // Keep going down to find the next visible modal one to shift the modal mask down under
32897         for (i = this.zIndexStack.length-1; i >= 0; --i) {
32898             comp = this.zIndexStack[i];
32899             if (!comp.hidden) {
32900                 if (!lastActivated) {
32901                     this._setActiveChild(comp);
32902                     lastActivated = true;
32903                 }
32904
32905                 // Move any modal mask down to just under the next modal floater down the stack
32906                 if (comp.modal) {
32907                     this._showModalMask(comp);
32908                     return;
32909                 }
32910             }
32911         }
32912
32913         // none to activate, so there must be no modal mask.
32914         // And clear the currently active property
32915         this._hideModalMask();
32916         if (!lastActivated) {
32917             this._setActiveChild(null);
32918         }
32919     },
32920
32921     _showModalMask: function(comp) {
32922         var zIndex = comp.el.getStyle('zIndex') - 4,
32923             maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : Ext.get(comp.getEl().dom.parentNode),
32924             parentBox;
32925         
32926         if (!maskTarget) {
32927             Ext.global.console && Ext.global.console.warn && Ext.global.console.warn('mask target could not be found. Mask cannot be shown');
32928             return;
32929         }
32930         
32931         parentBox = maskTarget.getBox();
32932
32933         if (!this.mask) {
32934             this.mask = Ext.getBody().createChild({
32935                 cls: Ext.baseCSSPrefix + 'mask'
32936             });
32937             this.mask.setVisibilityMode(Ext.Element.DISPLAY);
32938             this.mask.on('click', this._onMaskClick, this);
32939         }
32940         if (maskTarget.dom === document.body) {
32941             parentBox.height = Ext.Element.getViewHeight();
32942         }
32943         maskTarget.addCls(Ext.baseCSSPrefix + 'body-masked');
32944         this.mask.setBox(parentBox);
32945         this.mask.setStyle('zIndex', zIndex);
32946         this.mask.show();
32947     },
32948
32949     _hideModalMask: function() {
32950         if (this.mask && this.mask.dom.parentNode) {
32951             Ext.get(this.mask.dom.parentNode).removeCls(Ext.baseCSSPrefix + 'body-masked');
32952             this.mask.hide();
32953         }
32954     },
32955
32956     _onMaskClick: function() {
32957         if (this.front) {
32958             this.front.focus();
32959         }
32960     },
32961
32962     _onContainerResize: function() {
32963         if (this.mask && this.mask.isVisible()) {
32964             this.mask.setSize(Ext.get(this.mask.dom.parentNode).getViewSize(true));
32965         }
32966     },
32967
32968     /**
32969      * <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
32970      * need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
32971      * with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
32972      * <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
32973      * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
32974      * ZIndexManager in the desktop sample app:</p><code><pre>
32975 MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
32976 </pre></code>
32977      * @param {Ext.Component} comp The Component to register.
32978      */
32979     register : function(comp) {
32980         if (comp.zIndexManager) {
32981             comp.zIndexManager.unregister(comp);
32982         }
32983         comp.zIndexManager = this;
32984
32985         this.list[comp.id] = comp;
32986         this.zIndexStack.push(comp);
32987         comp.on('hide', this._activateLast, this);
32988     },
32989
32990     /**
32991      * <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
32992      * need to be called. Components are automatically unregistered upon destruction.
32993      * See {@link #register}.</p>
32994      * @param {Ext.Component} comp The Component to unregister.
32995      */
32996     unregister : function(comp) {
32997         delete comp.zIndexManager;
32998         if (this.list && this.list[comp.id]) {
32999             delete this.list[comp.id];
33000             comp.un('hide', this._activateLast);
33001             Ext.Array.remove(this.zIndexStack, comp);
33002
33003             // Destruction requires that the topmost visible floater be activated. Same as hiding.
33004             this._activateLast(comp);
33005         }
33006     },
33007
33008     /**
33009      * Gets a registered Component by id.
33010      * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
33011      * @return {Ext.Component}
33012      */
33013     get : function(id) {
33014         return typeof id == "object" ? id : this.list[id];
33015     },
33016
33017    /**
33018      * Brings the specified Component to the front of any other active Components in this ZIndexManager.
33019      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
33020      * @return {Boolean} True if the dialog was brought to the front, else false
33021      * if it was already in front
33022      */
33023     bringToFront : function(comp) {
33024         comp = this.get(comp);
33025         if (comp !== this.front) {
33026             Ext.Array.remove(this.zIndexStack, comp);
33027             this.zIndexStack.push(comp);
33028             this.assignZIndices();
33029             return true;
33030         }
33031         if (comp.modal) {
33032             this._showModalMask(comp);
33033         }
33034         return false;
33035     },
33036
33037     /**
33038      * Sends the specified Component to the back of other active Components in this ZIndexManager.
33039      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
33040      * @return {Ext.Component} The Component
33041      */
33042     sendToBack : function(comp) {
33043         comp = this.get(comp);
33044         Ext.Array.remove(this.zIndexStack, comp);
33045         this.zIndexStack.unshift(comp);
33046         this.assignZIndices();
33047         return comp;
33048     },
33049
33050     /**
33051      * Hides all Components managed by this ZIndexManager.
33052      */
33053     hideAll : function() {
33054         for (var id in this.list) {
33055             if (this.list[id].isComponent && this.list[id].isVisible()) {
33056                 this.list[id].hide();
33057             }
33058         }
33059     },
33060
33061     /**
33062      * @private
33063      * Temporarily hides all currently visible managed Components. This is for when
33064      * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
33065      * they should all be hidden just for the duration of the drag.
33066      */
33067     hide: function() {
33068         var i = 0,
33069             ln = this.zIndexStack.length,
33070             comp;
33071
33072         this.tempHidden = [];
33073         for (; i < ln; i++) {
33074             comp = this.zIndexStack[i];
33075             if (comp.isVisible()) {
33076                 this.tempHidden.push(comp);
33077                 comp.hide();
33078             }
33079         }
33080     },
33081
33082     /**
33083      * @private
33084      * Restores temporarily hidden managed Components to visibility.
33085      */
33086     show: function() {
33087         var i = 0,
33088             ln = this.tempHidden.length,
33089             comp,
33090             x,
33091             y;
33092
33093         for (; i < ln; i++) {
33094             comp = this.tempHidden[i];
33095             x = comp.x;
33096             y = comp.y;
33097             comp.show();
33098             comp.setPosition(x, y);
33099         }
33100         delete this.tempHidden;
33101     },
33102
33103     /**
33104      * Gets the currently-active Component in this ZIndexManager.
33105      * @return {Ext.Component} The active Component
33106      */
33107     getActive : function() {
33108         return this.front;
33109     },
33110
33111     /**
33112      * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
33113      * The function should accept a single {@link Ext.Component} reference as its only argument and should
33114      * return true if the Component matches the search criteria, otherwise it should return false.
33115      * @param {Function} fn The search function
33116      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
33117      * that gets passed to the function if not specified)
33118      * @return {Array} An array of zero or more matching windows
33119      */
33120     getBy : function(fn, scope) {
33121         var r = [],
33122             i = 0,
33123             len = this.zIndexStack.length,
33124             comp;
33125
33126         for (; i < len; i++) {
33127             comp = this.zIndexStack[i];
33128             if (fn.call(scope||comp, comp) !== false) {
33129                 r.push(comp);
33130             }
33131         }
33132         return r;
33133     },
33134
33135     /**
33136      * Executes the specified function once for every Component in this ZIndexManager, passing each
33137      * Component as the only parameter. Returning false from the function will stop the iteration.
33138      * @param {Function} fn The function to execute for each item
33139      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
33140      */
33141     each : function(fn, scope) {
33142         var comp;
33143         for (var id in this.list) {
33144             comp = this.list[id];
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 bottom and proceeding to the top.
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     eachBottomUp: function (fn, scope) {
33160         var comp,
33161             stack = this.zIndexStack,
33162             i, n;
33163
33164         for (i = 0, n = stack.length ; i < n; i++) {
33165             comp = stack[i];
33166             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
33167                 return;
33168             }
33169         }
33170     },
33171
33172     /**
33173      * Executes the specified function once for every Component in this ZIndexManager, passing each
33174      * Component as the only parameter. Returning false from the function will stop the iteration.
33175      * The components are passed to the function starting at the top and proceeding to the bottom.
33176      * @param {Function} fn The function to execute for each item
33177      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
33178      * is executed. Defaults to the current Component in the iteration.
33179      */
33180     eachTopDown: function (fn, scope) {
33181         var comp,
33182             stack = this.zIndexStack,
33183             i;
33184
33185         for (i = stack.length ; i-- > 0; ) {
33186             comp = stack[i];
33187             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
33188                 return;
33189             }
33190         }
33191     },
33192
33193     destroy: function() {
33194         this.each(function(c) {
33195             c.destroy();
33196         });
33197         delete this.zIndexStack;
33198         delete this.list;
33199         delete this.container;
33200         delete this.targetEl;
33201     }
33202 }, function() {
33203     /**
33204      * @class Ext.WindowManager
33205      * @extends Ext.ZIndexManager
33206      * <p>The default global floating Component group that is available automatically.</p>
33207      * <p>This manages instances of floating Components which were rendered programatically without
33208      * being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
33209      * <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
33210      * there are managed by that ZIndexManager.</p>
33211      * @singleton
33212      */
33213     Ext.WindowManager = Ext.WindowMgr = new this();
33214 });
33215
33216 /**
33217  * @private
33218  * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
33219  * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
33220  * for its container.
33221  */
33222 Ext.define('Ext.layout.container.boxOverflow.None', {
33223     
33224     alternateClassName: 'Ext.layout.boxOverflow.None',
33225     
33226     constructor: function(layout, config) {
33227         this.layout = layout;
33228         Ext.apply(this, config || {});
33229     },
33230
33231     handleOverflow: Ext.emptyFn,
33232
33233     clearOverflow: Ext.emptyFn,
33234     
33235     onRemove: Ext.emptyFn,
33236
33237     /**
33238      * @private
33239      * Normalizes an item reference, string id or numerical index into a reference to the item
33240      * @param {Ext.Component/String/Number} item The item reference, id or index
33241      * @return {Ext.Component} The item
33242      */
33243     getItem: function(item) {
33244         return this.layout.owner.getComponent(item);
33245     },
33246     
33247     onRemove: Ext.emptyFn
33248 });
33249 /**
33250  * @class Ext.util.KeyMap
33251  * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
33252  * The constructor accepts the same config object as defined by {@link #addBinding}.
33253  * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
33254  * combination it will call the function with this signature (if the match is a multi-key
33255  * combination the callback will still be called only once): (String key, Ext.EventObject e)
33256  * A KeyMap can also handle a string representation of keys. By default KeyMap starts enabled.<br />
33257  * Usage:
33258  <pre><code>
33259 // map one key by key code
33260 var map = new Ext.util.KeyMap("my-element", {
33261     key: 13, // or Ext.EventObject.ENTER
33262     fn: myHandler,
33263     scope: myObject
33264 });
33265
33266 // map multiple keys to one action by string
33267 var map = new Ext.util.KeyMap("my-element", {
33268     key: "a\r\n\t",
33269     fn: myHandler,
33270     scope: myObject
33271 });
33272
33273 // map multiple keys to multiple actions by strings and array of codes
33274 var map = new Ext.util.KeyMap("my-element", [
33275     {
33276         key: [10,13],
33277         fn: function(){ alert("Return was pressed"); }
33278     }, {
33279         key: "abc",
33280         fn: function(){ alert('a, b or c was pressed'); }
33281     }, {
33282         key: "\t",
33283         ctrl:true,
33284         shift:true,
33285         fn: function(){ alert('Control + shift + tab was pressed.'); }
33286     }
33287 ]);
33288 </code></pre>
33289  */
33290 Ext.define('Ext.util.KeyMap', {
33291     alternateClassName: 'Ext.KeyMap',
33292
33293     /**
33294      * Creates new KeyMap.
33295      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
33296      * @param {Object} binding The binding (see {@link #addBinding})
33297      * @param {String} [eventName="keydown"] The event to bind to
33298      */
33299     constructor: function(el, binding, eventName){
33300         var me = this;
33301
33302         Ext.apply(me, {
33303             el: Ext.get(el),
33304             eventName: eventName || me.eventName,
33305             bindings: []
33306         });
33307         if (binding) {
33308             me.addBinding(binding);
33309         }
33310         me.enable();
33311     },
33312
33313     eventName: 'keydown',
33314
33315     /**
33316      * Add a new binding to this KeyMap. The following config object properties are supported:
33317      * <pre>
33318 Property            Type             Description
33319 ----------          ---------------  ----------------------------------------------------------------------
33320 key                 String/Array     A single keycode or an array of keycodes to handle
33321 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)
33322 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)
33323 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)
33324 handler             Function         The function to call when KeyMap finds the expected key combination
33325 fn                  Function         Alias of handler (for backwards-compatibility)
33326 scope               Object           The scope of the callback function
33327 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.
33328 </pre>
33329      *
33330      * Usage:
33331      * <pre><code>
33332 // Create a KeyMap
33333 var map = new Ext.util.KeyMap(document, {
33334     key: Ext.EventObject.ENTER,
33335     fn: handleKey,
33336     scope: this
33337 });
33338
33339 //Add a new binding to the existing KeyMap later
33340 map.addBinding({
33341     key: 'abc',
33342     shift: true,
33343     fn: handleKey,
33344     scope: this
33345 });
33346 </code></pre>
33347      * @param {Object/Object[]} binding A single KeyMap config or an array of configs
33348      */
33349     addBinding : function(binding){
33350         if (Ext.isArray(binding)) {
33351             Ext.each(binding, this.addBinding, this);
33352             return;
33353         }
33354
33355         var keyCode = binding.key,
33356             processed = false,
33357             key,
33358             keys,
33359             keyString,
33360             i,
33361             len;
33362
33363         if (Ext.isString(keyCode)) {
33364             keys = [];
33365             keyString = keyCode.toUpperCase();
33366
33367             for (i = 0, len = keyString.length; i < len; ++i){
33368                 keys.push(keyString.charCodeAt(i));
33369             }
33370             keyCode = keys;
33371             processed = true;
33372         }
33373
33374         if (!Ext.isArray(keyCode)) {
33375             keyCode = [keyCode];
33376         }
33377
33378         if (!processed) {
33379             for (i = 0, len = keyCode.length; i < len; ++i) {
33380                 key = keyCode[i];
33381                 if (Ext.isString(key)) {
33382                     keyCode[i] = key.toUpperCase().charCodeAt(0);
33383                 }
33384             }
33385         }
33386
33387         this.bindings.push(Ext.apply({
33388             keyCode: keyCode
33389         }, binding));
33390     },
33391
33392     /**
33393      * Process any keydown events on the element
33394      * @private
33395      * @param {Ext.EventObject} event
33396      */
33397     handleKeyDown: function(event) {
33398         if (this.enabled) { //just in case
33399             var bindings = this.bindings,
33400                 i = 0,
33401                 len = bindings.length;
33402
33403             event = this.processEvent(event);
33404             for(; i < len; ++i){
33405                 this.processBinding(bindings[i], event);
33406             }
33407         }
33408     },
33409
33410     /**
33411      * Ugly hack to allow this class to be tested. Currently WebKit gives
33412      * no way to raise a key event properly with both
33413      * a) A keycode
33414      * b) The alt/ctrl/shift modifiers
33415      * So we have to simulate them here. Yuk!
33416      * This is a stub method intended to be overridden by tests.
33417      * More info: https://bugs.webkit.org/show_bug.cgi?id=16735
33418      * @private
33419      */
33420     processEvent: function(event){
33421         return event;
33422     },
33423
33424     /**
33425      * Process a particular binding and fire the handler if necessary.
33426      * @private
33427      * @param {Object} binding The binding information
33428      * @param {Ext.EventObject} event
33429      */
33430     processBinding: function(binding, event){
33431         if (this.checkModifiers(binding, event)) {
33432             var key = event.getKey(),
33433                 handler = binding.fn || binding.handler,
33434                 scope = binding.scope || this,
33435                 keyCode = binding.keyCode,
33436                 defaultEventAction = binding.defaultEventAction,
33437                 i,
33438                 len,
33439                 keydownEvent = new Ext.EventObjectImpl(event);
33440
33441
33442             for (i = 0, len = keyCode.length; i < len; ++i) {
33443                 if (key === keyCode[i]) {
33444                     if (handler.call(scope, key, event) !== true && defaultEventAction) {
33445                         keydownEvent[defaultEventAction]();
33446                     }
33447                     break;
33448                 }
33449             }
33450         }
33451     },
33452
33453     /**
33454      * Check if the modifiers on the event match those on the binding
33455      * @private
33456      * @param {Object} binding
33457      * @param {Ext.EventObject} event
33458      * @return {Boolean} True if the event matches the binding
33459      */
33460     checkModifiers: function(binding, e){
33461         var keys = ['shift', 'ctrl', 'alt'],
33462             i = 0,
33463             len = keys.length,
33464             val, key;
33465
33466         for (; i < len; ++i){
33467             key = keys[i];
33468             val = binding[key];
33469             if (!(val === undefined || (val === e[key + 'Key']))) {
33470                 return false;
33471             }
33472         }
33473         return true;
33474     },
33475
33476     /**
33477      * Shorthand for adding a single key listener
33478      * @param {Number/Number[]/Object} key Either the numeric key code, array of key codes or an object with the
33479      * following options:
33480      * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
33481      * @param {Function} fn The function to call
33482      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
33483      */
33484     on: function(key, fn, scope) {
33485         var keyCode, shift, ctrl, alt;
33486         if (Ext.isObject(key) && !Ext.isArray(key)) {
33487             keyCode = key.key;
33488             shift = key.shift;
33489             ctrl = key.ctrl;
33490             alt = key.alt;
33491         } else {
33492             keyCode = key;
33493         }
33494         this.addBinding({
33495             key: keyCode,
33496             shift: shift,
33497             ctrl: ctrl,
33498             alt: alt,
33499             fn: fn,
33500             scope: scope
33501         });
33502     },
33503
33504     /**
33505      * Returns true if this KeyMap is enabled
33506      * @return {Boolean}
33507      */
33508     isEnabled : function(){
33509         return this.enabled;
33510     },
33511
33512     /**
33513      * Enables this KeyMap
33514      */
33515     enable: function(){
33516         var me = this;
33517         
33518         if (!me.enabled) {
33519             me.el.on(me.eventName, me.handleKeyDown, me);
33520             me.enabled = true;
33521         }
33522     },
33523
33524     /**
33525      * Disable this KeyMap
33526      */
33527     disable: function(){
33528         var me = this;
33529         
33530         if (me.enabled) {
33531             me.el.removeListener(me.eventName, me.handleKeyDown, me);
33532             me.enabled = false;
33533         }
33534     },
33535
33536     /**
33537      * Convenience function for setting disabled/enabled by boolean.
33538      * @param {Boolean} disabled
33539      */
33540     setDisabled : function(disabled){
33541         if (disabled) {
33542             this.disable();
33543         } else {
33544             this.enable();
33545         }
33546     },
33547
33548     /**
33549      * Destroys the KeyMap instance and removes all handlers.
33550      * @param {Boolean} removeEl True to also remove the attached element
33551      */
33552     destroy: function(removeEl){
33553         var me = this;
33554
33555         me.bindings = [];
33556         me.disable();
33557         if (removeEl === true) {
33558             me.el.remove();
33559         }
33560         delete me.el;
33561     }
33562 });
33563 /**
33564  * @class Ext.util.ClickRepeater
33565  * @extends Ext.util.Observable
33566  *
33567  * A wrapper class which can be applied to any element. Fires a "click" event while the
33568  * mouse is pressed. The interval between firings may be specified in the config but
33569  * defaults to 20 milliseconds.
33570  *
33571  * Optionally, a CSS class may be applied to the element during the time it is pressed.
33572  *
33573  */
33574 Ext.define('Ext.util.ClickRepeater', {
33575     extend: 'Ext.util.Observable',
33576
33577     /**
33578      * Creates new ClickRepeater.
33579      * @param {String/HTMLElement/Ext.Element} el The element or its ID to listen on
33580      * @param {Object} config (optional) Config object.
33581      */
33582     constructor : function(el, config){
33583         this.el = Ext.get(el);
33584         this.el.unselectable();
33585
33586         Ext.apply(this, config);
33587
33588         this.addEvents(
33589         /**
33590          * @event mousedown
33591          * Fires when the mouse button is depressed.
33592          * @param {Ext.util.ClickRepeater} this
33593          * @param {Ext.EventObject} e
33594          */
33595         "mousedown",
33596         /**
33597          * @event click
33598          * Fires on a specified interval during the time the element is pressed.
33599          * @param {Ext.util.ClickRepeater} this
33600          * @param {Ext.EventObject} e
33601          */
33602         "click",
33603         /**
33604          * @event mouseup
33605          * Fires when the mouse key is released.
33606          * @param {Ext.util.ClickRepeater} this
33607          * @param {Ext.EventObject} e
33608          */
33609         "mouseup"
33610         );
33611
33612         if(!this.disabled){
33613             this.disabled = true;
33614             this.enable();
33615         }
33616
33617         // allow inline handler
33618         if(this.handler){
33619             this.on("click", this.handler,  this.scope || this);
33620         }
33621
33622         this.callParent();
33623     },
33624
33625     /**
33626      * @cfg {String/HTMLElement/Ext.Element} el The element to act as a button.
33627      */
33628
33629     /**
33630      * @cfg {String} pressedCls A CSS class name to be applied to the element while pressed.
33631      */
33632
33633     /**
33634      * @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
33635      * "interval" and "delay" are ignored.
33636      */
33637
33638     /**
33639      * @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
33640      */
33641     interval : 20,
33642
33643     /**
33644      * @cfg {Number} delay The initial delay before the repeating event begins firing.
33645      * Similar to an autorepeat key delay.
33646      */
33647     delay: 250,
33648
33649     /**
33650      * @cfg {Boolean} preventDefault True to prevent the default click event
33651      */
33652     preventDefault : true,
33653     /**
33654      * @cfg {Boolean} stopDefault True to stop the default click event
33655      */
33656     stopDefault : false,
33657
33658     timer : 0,
33659
33660     /**
33661      * Enables the repeater and allows events to fire.
33662      */
33663     enable: function(){
33664         if(this.disabled){
33665             this.el.on('mousedown', this.handleMouseDown, this);
33666             if (Ext.isIE){
33667                 this.el.on('dblclick', this.handleDblClick, this);
33668             }
33669             if(this.preventDefault || this.stopDefault){
33670                 this.el.on('click', this.eventOptions, this);
33671             }
33672         }
33673         this.disabled = false;
33674     },
33675
33676     /**
33677      * Disables the repeater and stops events from firing.
33678      */
33679     disable: function(/* private */ force){
33680         if(force || !this.disabled){
33681             clearTimeout(this.timer);
33682             if(this.pressedCls){
33683                 this.el.removeCls(this.pressedCls);
33684             }
33685             Ext.getDoc().un('mouseup', this.handleMouseUp, this);
33686             this.el.removeAllListeners();
33687         }
33688         this.disabled = true;
33689     },
33690
33691     /**
33692      * Convenience function for setting disabled/enabled by boolean.
33693      * @param {Boolean} disabled
33694      */
33695     setDisabled: function(disabled){
33696         this[disabled ? 'disable' : 'enable']();
33697     },
33698
33699     eventOptions: function(e){
33700         if(this.preventDefault){
33701             e.preventDefault();
33702         }
33703         if(this.stopDefault){
33704             e.stopEvent();
33705         }
33706     },
33707
33708     // private
33709     destroy : function() {
33710         this.disable(true);
33711         Ext.destroy(this.el);
33712         this.clearListeners();
33713     },
33714
33715     handleDblClick : function(e){
33716         clearTimeout(this.timer);
33717         this.el.blur();
33718
33719         this.fireEvent("mousedown", this, e);
33720         this.fireEvent("click", this, e);
33721     },
33722
33723     // private
33724     handleMouseDown : function(e){
33725         clearTimeout(this.timer);
33726         this.el.blur();
33727         if(this.pressedCls){
33728             this.el.addCls(this.pressedCls);
33729         }
33730         this.mousedownTime = new Date();
33731
33732         Ext.getDoc().on("mouseup", this.handleMouseUp, this);
33733         this.el.on("mouseout", this.handleMouseOut, this);
33734
33735         this.fireEvent("mousedown", this, e);
33736         this.fireEvent("click", this, e);
33737
33738         // Do not honor delay or interval if acceleration wanted.
33739         if (this.accelerate) {
33740             this.delay = 400;
33741         }
33742
33743         // Re-wrap the event object in a non-shared object, so it doesn't lose its context if
33744         // the global shared EventObject gets a new Event put into it before the timer fires.
33745         e = new Ext.EventObjectImpl(e);
33746
33747         this.timer =  Ext.defer(this.click, this.delay || this.interval, this, [e]);
33748     },
33749
33750     // private
33751     click : function(e){
33752         this.fireEvent("click", this, e);
33753         this.timer =  Ext.defer(this.click, this.accelerate ?
33754             this.easeOutExpo(Ext.Date.getElapsed(this.mousedownTime),
33755                 400,
33756                 -390,
33757                 12000) :
33758             this.interval, this, [e]);
33759     },
33760
33761     easeOutExpo : function (t, b, c, d) {
33762         return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
33763     },
33764
33765     // private
33766     handleMouseOut : function(){
33767         clearTimeout(this.timer);
33768         if(this.pressedCls){
33769             this.el.removeCls(this.pressedCls);
33770         }
33771         this.el.on("mouseover", this.handleMouseReturn, this);
33772     },
33773
33774     // private
33775     handleMouseReturn : function(){
33776         this.el.un("mouseover", this.handleMouseReturn, this);
33777         if(this.pressedCls){
33778             this.el.addCls(this.pressedCls);
33779         }
33780         this.click();
33781     },
33782
33783     // private
33784     handleMouseUp : function(e){
33785         clearTimeout(this.timer);
33786         this.el.un("mouseover", this.handleMouseReturn, this);
33787         this.el.un("mouseout", this.handleMouseOut, this);
33788         Ext.getDoc().un("mouseup", this.handleMouseUp, this);
33789         if(this.pressedCls){
33790             this.el.removeCls(this.pressedCls);
33791         }
33792         this.fireEvent("mouseup", this, e);
33793     }
33794 });
33795
33796 /**
33797  * @class Ext.layout.component.Component
33798  * @extends Ext.layout.Layout
33799  *
33800  * This class is intended to be extended or created via the {@link Ext.Component#componentLayout layout}
33801  * configuration property.  See {@link Ext.Component#componentLayout} for additional details.
33802  *
33803  * @private
33804  */
33805 Ext.define('Ext.layout.component.Component', {
33806
33807     /* Begin Definitions */
33808
33809     extend: 'Ext.layout.Layout',
33810
33811     /* End Definitions */
33812
33813     type: 'component',
33814
33815     monitorChildren: true,
33816
33817     initLayout : function() {
33818         var me = this,
33819             owner = me.owner,
33820             ownerEl = owner.el;
33821
33822         if (!me.initialized) {
33823             if (owner.frameSize) {
33824                 me.frameSize = owner.frameSize;
33825             }
33826             else {
33827                 owner.frameSize = me.frameSize = {
33828                     top: 0,
33829                     left: 0,
33830                     bottom: 0,
33831                     right: 0
33832                 };
33833             }
33834         }
33835         me.callParent(arguments);
33836     },
33837
33838     beforeLayout : function(width, height, isSetSize, callingContainer) {
33839         this.callParent(arguments);
33840
33841         var me = this,
33842             owner = me.owner,
33843             ownerCt = owner.ownerCt,
33844             layout = owner.layout,
33845             isVisible = owner.isVisible(true),
33846             ownerElChild = owner.el.child,
33847             layoutCollection;
33848
33849         // Cache the size we began with so we can see if there has been any effect.
33850         me.previousComponentSize = me.lastComponentSize;
33851
33852         // Do not allow autoing of any dimensions which are fixed
33853         if (!isSetSize
33854             && ((!Ext.isNumber(width) && owner.isFixedWidth()) ||
33855                 (!Ext.isNumber(height) && owner.isFixedHeight()))
33856             // unless we are being told to do so by the ownerCt's layout
33857             && callingContainer && callingContainer !== ownerCt) {
33858             
33859             me.doContainerLayout();
33860             return false;
33861         }
33862
33863         // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
33864         // If the owner itself is a directly hidden floater, set the needsLayout object on that for when it is shown.
33865         if (!isVisible && (owner.hiddenAncestor || owner.floating)) {
33866             if (owner.hiddenAncestor) {
33867                 layoutCollection = owner.hiddenAncestor.layoutOnShow;
33868                 layoutCollection.remove(owner);
33869                 layoutCollection.add(owner);
33870             }
33871             owner.needsLayout = {
33872                 width: width,
33873                 height: height,
33874                 isSetSize: false
33875             };
33876         }
33877
33878         if (isVisible && this.needsLayout(width, height)) {
33879             return owner.beforeComponentLayout(width, height, isSetSize, callingContainer);
33880         }
33881         else {
33882             return false;
33883         }
33884     },
33885
33886     /**
33887     * Check if the new size is different from the current size and only
33888     * trigger a layout if it is necessary.
33889     * @param {Number} width The new width to set.
33890     * @param {Number} height The new height to set.
33891     */
33892     needsLayout : function(width, height) {
33893         var me = this,
33894             widthBeingChanged,
33895             heightBeingChanged;
33896             me.lastComponentSize = me.lastComponentSize || {
33897                 width: -Infinity,
33898                 height: -Infinity
33899             };
33900
33901         // If autoWidthing, or an explicitly different width is passed, then the width is being changed.
33902         widthBeingChanged  = !Ext.isDefined(width)  || me.lastComponentSize.width  !== width;
33903
33904         // If autoHeighting, or an explicitly different height is passed, then the height is being changed.
33905         heightBeingChanged = !Ext.isDefined(height) || me.lastComponentSize.height !== height;
33906
33907
33908         // isSizing flag added to prevent redundant layouts when going up the layout chain
33909         return !me.isSizing && (me.childrenChanged || widthBeingChanged || heightBeingChanged);
33910     },
33911
33912     /**
33913     * Set the size of any element supporting undefined, null, and values.
33914     * @param {Number} width The new width to set.
33915     * @param {Number} height The new height to set.
33916     */
33917     setElementSize: function(el, width, height) {
33918         if (width !== undefined && height !== undefined) {
33919             el.setSize(width, height);
33920         }
33921         else if (height !== undefined) {
33922             el.setHeight(height);
33923         }
33924         else if (width !== undefined) {
33925             el.setWidth(width);
33926         }
33927     },
33928
33929     /**
33930      * Returns the owner component's resize element.
33931      * @return {Ext.Element}
33932      */
33933      getTarget : function() {
33934          return this.owner.el;
33935      },
33936
33937     /**
33938      * <p>Returns the element into which rendering must take place. Defaults to the owner Component's encapsulating element.</p>
33939      * May be overridden in Component layout managers which implement an inner element.
33940      * @return {Ext.Element}
33941      */
33942     getRenderTarget : function() {
33943         return this.owner.el;
33944     },
33945
33946     /**
33947     * Set the size of the target element.
33948     * @param {Number} width The new width to set.
33949     * @param {Number} height The new height to set.
33950     */
33951     setTargetSize : function(width, height) {
33952         var me = this;
33953         me.setElementSize(me.owner.el, width, height);
33954
33955         if (me.owner.frameBody) {
33956             var targetInfo = me.getTargetInfo(),
33957                 padding = targetInfo.padding,
33958                 border = targetInfo.border,
33959                 frameSize = me.frameSize;
33960
33961             me.setElementSize(me.owner.frameBody,
33962                 Ext.isNumber(width) ? (width - frameSize.left - frameSize.right - padding.left - padding.right - border.left - border.right) : width,
33963                 Ext.isNumber(height) ? (height - frameSize.top - frameSize.bottom - padding.top - padding.bottom - border.top - border.bottom) : height
33964             );
33965         }
33966
33967         me.autoSized = {
33968             width: !Ext.isNumber(width),
33969             height: !Ext.isNumber(height)
33970         };
33971
33972         me.lastComponentSize = {
33973             width: width,
33974             height: height
33975         };
33976     },
33977
33978     getTargetInfo : function() {
33979         if (!this.targetInfo) {
33980             var target = this.getTarget(),
33981                 body = this.owner.getTargetEl();
33982
33983             this.targetInfo = {
33984                 padding: {
33985                     top: target.getPadding('t'),
33986                     right: target.getPadding('r'),
33987                     bottom: target.getPadding('b'),
33988                     left: target.getPadding('l')
33989                 },
33990                 border: {
33991                     top: target.getBorderWidth('t'),
33992                     right: target.getBorderWidth('r'),
33993                     bottom: target.getBorderWidth('b'),
33994                     left: target.getBorderWidth('l')
33995                 },
33996                 bodyMargin: {
33997                     top: body.getMargin('t'),
33998                     right: body.getMargin('r'),
33999                     bottom: body.getMargin('b'),
34000                     left: body.getMargin('l')
34001                 }
34002             };
34003         }
34004         return this.targetInfo;
34005     },
34006
34007     // Start laying out UP the ownerCt's layout when flagged to do so.
34008     doOwnerCtLayouts: function() {
34009         var owner = this.owner,
34010             ownerCt = owner.ownerCt,
34011             ownerCtComponentLayout, ownerCtContainerLayout,
34012             curSize = this.lastComponentSize,
34013             prevSize = this.previousComponentSize,
34014             widthChange  = (prevSize && curSize && Ext.isNumber(curSize.width )) ? curSize.width  !== prevSize.width  : true,
34015             heightChange = (prevSize && curSize && Ext.isNumber(curSize.height)) ? curSize.height !== prevSize.height : true;
34016
34017         // If size has not changed, do not inform upstream layouts
34018         if (!ownerCt || (!widthChange && !heightChange)) {
34019             return;
34020         }
34021
34022         ownerCtComponentLayout = ownerCt.componentLayout;
34023         ownerCtContainerLayout = ownerCt.layout;
34024
34025         if (!owner.floating && ownerCtComponentLayout && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
34026             if (!ownerCt.suspendLayout && ownerCtContainerLayout && !ownerCtContainerLayout.layoutBusy) {
34027
34028                 // If the owning Container may be adjusted in any of the the dimension which have changed, perform its Component layout
34029                 if (((widthChange && !ownerCt.isFixedWidth()) || (heightChange && !ownerCt.isFixedHeight()))) {
34030                     // Set the isSizing flag so that the upstream Container layout (called after a Component layout) can omit this component from sizing operations
34031                     this.isSizing = true;
34032                     ownerCt.doComponentLayout();
34033                     this.isSizing = false;
34034                 }
34035                 // Execute upstream Container layout
34036                 else if (ownerCtContainerLayout.bindToOwnerCtContainer === true) {
34037                     ownerCtContainerLayout.layout();
34038                 }
34039             }
34040         }
34041     },
34042
34043     doContainerLayout: function() {
34044         var me = this,
34045             owner = me.owner,
34046             ownerCt = owner.ownerCt,
34047             layout = owner.layout,
34048             ownerCtComponentLayout;
34049
34050         // Run the container layout if it exists (layout for child items)
34051         // **Unless automatic laying out is suspended, or the layout is currently running**
34052         if (!owner.suspendLayout && layout && layout.isLayout && !layout.layoutBusy && !layout.isAutoDock) {
34053             layout.layout();
34054         }
34055
34056         // Tell the ownerCt that it's child has changed and can be re-layed by ignoring the lastComponentSize cache.
34057         if (ownerCt && ownerCt.componentLayout) {
34058             ownerCtComponentLayout = ownerCt.componentLayout;
34059             if (!owner.floating && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
34060                 ownerCtComponentLayout.childrenChanged = true;
34061             }
34062         }
34063     },
34064
34065     afterLayout : function(width, height, isSetSize, layoutOwner) {
34066         this.doContainerLayout();
34067         this.owner.afterComponentLayout(width, height, isSetSize, layoutOwner);
34068     }
34069 });
34070
34071 /**
34072  * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
34073  * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
34074  * should not contain any HTML, otherwise it may not be measured correctly.
34075  *
34076  * The measurement works by copying the relevant CSS styles that can affect the font related display, 
34077  * then checking the size of an element that is auto-sized. Note that if the text is multi-lined, you must 
34078  * provide a **fixed width** when doing the measurement.
34079  *
34080  * If multiple measurements are being done on the same element, you create a new instance to initialize 
34081  * to avoid the overhead of copying the styles to the element repeatedly.
34082  */
34083 Ext.define('Ext.util.TextMetrics', {
34084     statics: {
34085         shared: null,
34086         /**
34087          * Measures the size of the specified text
34088          * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
34089          * that can affect the size of the rendered text
34090          * @param {String} text The text to measure
34091          * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
34092          * in order to accurately measure the text height
34093          * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
34094          */
34095         measure: function(el, text, fixedWidth){
34096             var me = this,
34097                 shared = me.shared;
34098             
34099             if(!shared){
34100                 shared = me.shared = new me(el, fixedWidth);
34101             }
34102             shared.bind(el);
34103             shared.setFixedWidth(fixedWidth || 'auto');
34104             return shared.getSize(text);
34105         },
34106         
34107         /**
34108           * Destroy the TextMetrics instance created by {@link #measure}.
34109           */
34110          destroy: function(){
34111              var me = this;
34112              Ext.destroy(me.shared);
34113              me.shared = null;
34114          }
34115     },
34116     
34117     /**
34118      * Creates new TextMetrics.
34119      * @param {String/HTMLElement/Ext.Element} bindTo The element or its ID to bind to.
34120      * @param {Number} fixedWidth (optional) A fixed width to apply to the measuring element.
34121      */
34122     constructor: function(bindTo, fixedWidth){
34123         var measure = this.measure = Ext.getBody().createChild({
34124             cls: 'x-textmetrics'
34125         });
34126         this.el = Ext.get(bindTo);
34127         
34128         measure.position('absolute');
34129         measure.setLeftTop(-1000, -1000);
34130         measure.hide();
34131
34132         if (fixedWidth) {
34133            measure.setWidth(fixedWidth);
34134         }
34135     },
34136     
34137     /**
34138      * Returns the size of the specified text based on the internal element's style and width properties
34139      * @param {String} text The text to measure
34140      * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
34141      */
34142     getSize: function(text){
34143         var measure = this.measure,
34144             size;
34145         
34146         measure.update(text);
34147         size = measure.getSize();
34148         measure.update('');
34149         return size;
34150     },
34151     
34152     /**
34153      * Binds this TextMetrics instance to a new element
34154      * @param {String/HTMLElement/Ext.Element} el The element or its ID.
34155      */
34156     bind: function(el){
34157         var me = this;
34158         
34159         me.el = Ext.get(el);
34160         me.measure.setStyle(
34161             me.el.getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
34162         );
34163     },
34164     
34165     /**
34166      * Sets a fixed width on the internal measurement element.  If the text will be multiline, you have
34167      * to set a fixed width in order to accurately measure the text height.
34168      * @param {Number} width The width to set on the element
34169      */
34170      setFixedWidth : function(width){
34171          this.measure.setWidth(width);
34172      },
34173      
34174      /**
34175       * Returns the measured width of the specified text
34176       * @param {String} text The text to measure
34177       * @return {Number} width The width in pixels
34178       */
34179      getWidth : function(text){
34180          this.measure.dom.style.width = 'auto';
34181          return this.getSize(text).width;
34182      },
34183      
34184      /**
34185       * Returns the measured height of the specified text
34186       * @param {String} text The text to measure
34187       * @return {Number} height The height in pixels
34188       */
34189      getHeight : function(text){
34190          return this.getSize(text).height;
34191      },
34192      
34193      /**
34194       * Destroy this instance
34195       */
34196      destroy: function(){
34197          var me = this;
34198          me.measure.remove();
34199          delete me.el;
34200          delete me.measure;
34201      }
34202 }, function(){
34203     Ext.Element.addMethods({
34204         /**
34205          * Returns the width in pixels of the passed text, or the width of the text in this Element.
34206          * @param {String} text The text to measure. Defaults to the innerHTML of the element.
34207          * @param {Number} min (optional) The minumum value to return.
34208          * @param {Number} max (optional) The maximum value to return.
34209          * @return {Number} The text width in pixels.
34210          * @member Ext.Element
34211          */
34212         getTextWidth : function(text, min, max){
34213             return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000);
34214         }
34215     });
34216 });
34217
34218 /**
34219  * @class Ext.layout.container.boxOverflow.Scroller
34220  * @extends Ext.layout.container.boxOverflow.None
34221  * @private
34222  */
34223 Ext.define('Ext.layout.container.boxOverflow.Scroller', {
34224
34225     /* Begin Definitions */
34226
34227     extend: 'Ext.layout.container.boxOverflow.None',
34228     requires: ['Ext.util.ClickRepeater', 'Ext.Element'],
34229     alternateClassName: 'Ext.layout.boxOverflow.Scroller',
34230     mixins: {
34231         observable: 'Ext.util.Observable'
34232     },
34233     
34234     /* End Definitions */
34235
34236     /**
34237      * @cfg {Boolean} animateScroll
34238      * True to animate the scrolling of items within the layout (ignored if enableScroll is false)
34239      */
34240     animateScroll: false,
34241
34242     /**
34243      * @cfg {Number} scrollIncrement
34244      * The number of pixels to scroll by on scroller click
34245      */
34246     scrollIncrement: 20,
34247
34248     /**
34249      * @cfg {Number} wheelIncrement
34250      * The number of pixels to increment on mouse wheel scrolling.
34251      */
34252     wheelIncrement: 10,
34253
34254     /**
34255      * @cfg {Number} scrollRepeatInterval
34256      * Number of milliseconds between each scroll while a scroller button is held down
34257      */
34258     scrollRepeatInterval: 60,
34259
34260     /**
34261      * @cfg {Number} scrollDuration
34262      * Number of milliseconds that each scroll animation lasts
34263      */
34264     scrollDuration: 400,
34265
34266     /**
34267      * @cfg {String} beforeCtCls
34268      * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
34269      * which must always be present at the leftmost edge of the Container
34270      */
34271
34272     /**
34273      * @cfg {String} afterCtCls
34274      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
34275      * which must always be present at the rightmost edge of the Container
34276      */
34277
34278     /**
34279      * @cfg {String} [scrollerCls='x-box-scroller']
34280      * CSS class added to both scroller elements if enableScroll is used
34281      */
34282     scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
34283
34284     /**
34285      * @cfg {String} beforeScrollerCls
34286      * CSS class added to the left scroller element if enableScroll is used
34287      */
34288
34289     /**
34290      * @cfg {String} afterScrollerCls
34291      * CSS class added to the right scroller element if enableScroll is used
34292      */
34293     
34294     constructor: function(layout, config) {
34295         this.layout = layout;
34296         Ext.apply(this, config || {});
34297         
34298         this.addEvents(
34299             /**
34300              * @event scroll
34301              * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
34302              * @param {Number} newPosition The new position of the scroller
34303              * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
34304              */
34305             'scroll'
34306         );
34307     },
34308     
34309     initCSSClasses: function() {
34310         var me = this,
34311         layout = me.layout;
34312
34313         if (!me.CSSinitialized) {
34314             me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
34315             me.afterCtCls  = me.afterCtCls  || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
34316             me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
34317             me.afterScrollerCls  = me.afterScrollerCls  || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
34318             me.CSSinitializes = true;
34319         }
34320     },
34321
34322     handleOverflow: function(calculations, targetSize) {
34323         var me = this,
34324             layout = me.layout,
34325             methodName = 'get' + layout.parallelPrefixCap,
34326             newSize = {};
34327
34328         me.initCSSClasses();
34329         me.callParent(arguments);
34330         this.createInnerElements();
34331         this.showScrollers();
34332         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
34333         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
34334         return { targetSize: newSize };
34335     },
34336
34337     /**
34338      * @private
34339      * Creates the beforeCt and afterCt elements if they have not already been created
34340      */
34341     createInnerElements: function() {
34342         var me = this,
34343             target = me.layout.getRenderTarget();
34344
34345         //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
34346         //special items such as scrollers or dropdown menu triggers
34347         if (!me.beforeCt) {
34348             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
34349             me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
34350             me.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls},  'after');
34351             me.createWheelListener();
34352         }
34353     },
34354
34355     /**
34356      * @private
34357      * Sets up an listener to scroll on the layout's innerCt mousewheel event
34358      */
34359     createWheelListener: function() {
34360         this.layout.innerCt.on({
34361             scope     : this,
34362             mousewheel: function(e) {
34363                 e.stopEvent();
34364
34365                 this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
34366             }
34367         });
34368     },
34369
34370     /**
34371      * @private
34372      */
34373     clearOverflow: function() {
34374         this.hideScrollers();
34375     },
34376
34377     /**
34378      * @private
34379      * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
34380      * present. 
34381      */
34382     showScrollers: function() {
34383         this.createScrollers();
34384         this.beforeScroller.show();
34385         this.afterScroller.show();
34386         this.updateScrollButtons();
34387         
34388         this.layout.owner.addClsWithUI('scroller');
34389     },
34390
34391     /**
34392      * @private
34393      * Hides the scroller elements in the beforeCt and afterCt
34394      */
34395     hideScrollers: function() {
34396         if (this.beforeScroller != undefined) {
34397             this.beforeScroller.hide();
34398             this.afterScroller.hide();
34399             
34400             this.layout.owner.removeClsWithUI('scroller');
34401         }
34402     },
34403
34404     /**
34405      * @private
34406      * Creates the clickable scroller elements and places them into the beforeCt and afterCt
34407      */
34408     createScrollers: function() {
34409         if (!this.beforeScroller && !this.afterScroller) {
34410             var before = this.beforeCt.createChild({
34411                 cls: Ext.String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
34412             });
34413
34414             var after = this.afterCt.createChild({
34415                 cls: Ext.String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
34416             });
34417
34418             before.addClsOnOver(this.beforeScrollerCls + '-hover');
34419             after.addClsOnOver(this.afterScrollerCls + '-hover');
34420
34421             before.setVisibilityMode(Ext.Element.DISPLAY);
34422             after.setVisibilityMode(Ext.Element.DISPLAY);
34423
34424             this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
34425                 interval: this.scrollRepeatInterval,
34426                 handler : this.scrollLeft,
34427                 scope   : this
34428             });
34429
34430             this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
34431                 interval: this.scrollRepeatInterval,
34432                 handler : this.scrollRight,
34433                 scope   : this
34434             });
34435
34436             /**
34437              * @property beforeScroller
34438              * @type Ext.Element
34439              * The left scroller element. Only created when needed.
34440              */
34441             this.beforeScroller = before;
34442
34443             /**
34444              * @property afterScroller
34445              * @type Ext.Element
34446              * The left scroller element. Only created when needed.
34447              */
34448             this.afterScroller = after;
34449         }
34450     },
34451
34452     /**
34453      * @private
34454      */
34455     destroy: function() {
34456         Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
34457     },
34458
34459     /**
34460      * @private
34461      * Scrolls left or right by the number of pixels specified
34462      * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
34463      */
34464     scrollBy: function(delta, animate) {
34465         this.scrollTo(this.getScrollPosition() + delta, animate);
34466     },
34467
34468     /**
34469      * @private
34470      * @return {Object} Object passed to scrollTo when scrolling
34471      */
34472     getScrollAnim: function() {
34473         return {
34474             duration: this.scrollDuration, 
34475             callback: this.updateScrollButtons, 
34476             scope   : this
34477         };
34478     },
34479
34480     /**
34481      * @private
34482      * Enables or disables each scroller button based on the current scroll position
34483      */
34484     updateScrollButtons: function() {
34485         if (this.beforeScroller == undefined || this.afterScroller == undefined) {
34486             return;
34487         }
34488
34489         var beforeMeth = this.atExtremeBefore()  ? 'addCls' : 'removeCls',
34490             afterMeth  = this.atExtremeAfter() ? 'addCls' : 'removeCls',
34491             beforeCls  = this.beforeScrollerCls + '-disabled',
34492             afterCls   = this.afterScrollerCls  + '-disabled';
34493         
34494         this.beforeScroller[beforeMeth](beforeCls);
34495         this.afterScroller[afterMeth](afterCls);
34496         this.scrolling = false;
34497     },
34498
34499     /**
34500      * @private
34501      * Returns true if the innerCt scroll is already at its left-most point
34502      * @return {Boolean} True if already at furthest left point
34503      */
34504     atExtremeBefore: function() {
34505         return this.getScrollPosition() === 0;
34506     },
34507
34508     /**
34509      * @private
34510      * Scrolls to the left by the configured amount
34511      */
34512     scrollLeft: function() {
34513         this.scrollBy(-this.scrollIncrement, false);
34514     },
34515
34516     /**
34517      * @private
34518      * Scrolls to the right by the configured amount
34519      */
34520     scrollRight: function() {
34521         this.scrollBy(this.scrollIncrement, false);
34522     },
34523
34524     /**
34525      * Returns the current scroll position of the innerCt element
34526      * @return {Number} The current scroll position
34527      */
34528     getScrollPosition: function(){
34529         var layout = this.layout;
34530         return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
34531     },
34532
34533     /**
34534      * @private
34535      * Returns the maximum value we can scrollTo
34536      * @return {Number} The max scroll value
34537      */
34538     getMaxScrollPosition: function() {
34539         var layout = this.layout;
34540         return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
34541     },
34542
34543     /**
34544      * @private
34545      * Returns true if the innerCt scroll is already at its right-most point
34546      * @return {Boolean} True if already at furthest right point
34547      */
34548     atExtremeAfter: function() {
34549         return this.getScrollPosition() >= this.getMaxScrollPosition();
34550     },
34551
34552     /**
34553      * @private
34554      * Scrolls to the given position. Performs bounds checking.
34555      * @param {Number} position The position to scroll to. This is constrained.
34556      * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
34557      */
34558     scrollTo: function(position, animate) {
34559         var me = this,
34560             layout = me.layout,
34561             oldPosition = me.getScrollPosition(),
34562             newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
34563
34564         if (newPosition != oldPosition && !me.scrolling) {
34565             if (animate == undefined) {
34566                 animate = me.animateScroll;
34567             }
34568
34569             layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
34570             if (animate) {
34571                 me.scrolling = true;
34572             } else {
34573                 me.scrolling = false;
34574                 me.updateScrollButtons();
34575             }
34576             
34577             me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
34578         }
34579     },
34580
34581     /**
34582      * Scrolls to the given component.
34583      * @param {String/Number/Ext.Component} item The item to scroll to. Can be a numerical index, component id 
34584      * or a reference to the component itself.
34585      * @param {Boolean} animate True to animate the scrolling
34586      */
34587     scrollToItem: function(item, animate) {
34588         var me = this,
34589             layout = me.layout,
34590             visibility,
34591             box,
34592             newPos;
34593
34594         item = me.getItem(item);
34595         if (item != undefined) {
34596             visibility = this.getItemVisibility(item);
34597             if (!visibility.fullyVisible) {
34598                 box  = item.getBox(true, true);
34599                 newPos = box[layout.parallelPosition];
34600                 if (visibility.hiddenEnd) {
34601                     newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
34602                 }
34603                 this.scrollTo(newPos, animate);
34604             }
34605         }
34606     },
34607
34608     /**
34609      * @private
34610      * For a given item in the container, return an object with information on whether the item is visible
34611      * with the current innerCt scroll value.
34612      * @param {Ext.Component} item The item
34613      * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
34614      */
34615     getItemVisibility: function(item) {
34616         var me          = this,
34617             box         = me.getItem(item).getBox(true, true),
34618             layout      = me.layout,
34619             itemStart   = box[layout.parallelPosition],
34620             itemEnd     = itemStart + box[layout.parallelPrefix],
34621             scrollStart = me.getScrollPosition(),
34622             scrollEnd   = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
34623
34624         return {
34625             hiddenStart : itemStart < scrollStart,
34626             hiddenEnd   : itemEnd > scrollEnd,
34627             fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd
34628         };
34629     }
34630 });
34631 /**
34632  * @class Ext.util.Offset
34633  * @ignore
34634  */
34635 Ext.define('Ext.util.Offset', {
34636
34637     /* Begin Definitions */
34638
34639     statics: {
34640         fromObject: function(obj) {
34641             return new this(obj.x, obj.y);
34642         }
34643     },
34644
34645     /* End Definitions */
34646
34647     constructor: function(x, y) {
34648         this.x = (x != null && !isNaN(x)) ? x : 0;
34649         this.y = (y != null && !isNaN(y)) ? y : 0;
34650
34651         return this;
34652     },
34653
34654     copy: function() {
34655         return new Ext.util.Offset(this.x, this.y);
34656     },
34657
34658     copyFrom: function(p) {
34659         this.x = p.x;
34660         this.y = p.y;
34661     },
34662
34663     toString: function() {
34664         return "Offset[" + this.x + "," + this.y + "]";
34665     },
34666
34667     equals: function(offset) {
34668         if(!(offset instanceof this.statics())) {
34669             Ext.Error.raise('Offset must be an instance of Ext.util.Offset');
34670         }
34671
34672         return (this.x == offset.x && this.y == offset.y);
34673     },
34674
34675     round: function(to) {
34676         if (!isNaN(to)) {
34677             var factor = Math.pow(10, to);
34678             this.x = Math.round(this.x * factor) / factor;
34679             this.y = Math.round(this.y * factor) / factor;
34680         } else {
34681             this.x = Math.round(this.x);
34682             this.y = Math.round(this.y);
34683         }
34684     },
34685
34686     isZero: function() {
34687         return this.x == 0 && this.y == 0;
34688     }
34689 });
34690
34691 /**
34692  * @class Ext.util.KeyNav
34693  * <p>Provides a convenient wrapper for normalized keyboard navigation.  KeyNav allows you to bind
34694  * navigation keys to function calls that will get called when the keys are pressed, providing an easy
34695  * way to implement custom navigation schemes for any UI component.</p>
34696  * <p>The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,
34697  * pageUp, pageDown, del, backspace, home, end.  Usage:</p>
34698  <pre><code>
34699 var nav = new Ext.util.KeyNav("my-element", {
34700     "left" : function(e){
34701         this.moveLeft(e.ctrlKey);
34702     },
34703     "right" : function(e){
34704         this.moveRight(e.ctrlKey);
34705     },
34706     "enter" : function(e){
34707         this.save();
34708     },
34709     scope : this
34710 });
34711 </code></pre>
34712  */
34713 Ext.define('Ext.util.KeyNav', {
34714     
34715     alternateClassName: 'Ext.KeyNav',
34716     
34717     requires: ['Ext.util.KeyMap'],
34718     
34719     statics: {
34720         keyOptions: {
34721             left: 37,
34722             right: 39,
34723             up: 38,
34724             down: 40,
34725             space: 32,
34726             pageUp: 33,
34727             pageDown: 34,
34728             del: 46,
34729             backspace: 8,
34730             home: 36,
34731             end: 35,
34732             enter: 13,
34733             esc: 27,
34734             tab: 9
34735         }
34736     },
34737
34738     /**
34739      * Creates new KeyNav.
34740      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
34741      * @param {Object} config The config
34742      */
34743     constructor: function(el, config){
34744         this.setConfig(el, config || {});
34745     },
34746     
34747     /**
34748      * Sets up a configuration for the KeyNav.
34749      * @private
34750      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
34751      * @param {Object} config A configuration object as specified in the constructor.
34752      */
34753     setConfig: function(el, config) {
34754         if (this.map) {
34755             this.map.destroy();
34756         }
34757         
34758         var map = Ext.create('Ext.util.KeyMap', el, null, this.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : this.forceKeyDown)),
34759             keys = Ext.util.KeyNav.keyOptions,
34760             scope = config.scope || this,
34761             key;
34762         
34763         this.map = map;
34764         for (key in keys) {
34765             if (keys.hasOwnProperty(key)) {
34766                 if (config[key]) {
34767                     map.addBinding({
34768                         scope: scope,
34769                         key: keys[key],
34770                         handler: Ext.Function.bind(this.handleEvent, scope, [config[key]], true),
34771                         defaultEventAction: config.defaultEventAction || this.defaultEventAction
34772                     });
34773                 }
34774             }
34775         }
34776         
34777         map.disable();
34778         if (!config.disabled) {
34779             map.enable();
34780         }
34781     },
34782     
34783     /**
34784      * Method for filtering out the map argument
34785      * @private
34786      * @param {Ext.util.KeyMap} map
34787      * @param {Ext.EventObject} event
34788      * @param {Object} options Contains the handler to call
34789      */
34790     handleEvent: function(map, event, handler){
34791         return handler.call(this, event);
34792     },
34793     
34794     /**
34795      * @cfg {Boolean} disabled
34796      * True to disable this KeyNav instance.
34797      */
34798     disabled: false,
34799     
34800     /**
34801      * @cfg {String} defaultEventAction
34802      * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key.  Valid values are
34803      * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
34804      * {@link Ext.EventObject#stopPropagation}.
34805      */
34806     defaultEventAction: "stopEvent",
34807     
34808     /**
34809      * @cfg {Boolean} forceKeyDown
34810      * Handle the keydown event instead of keypress.  KeyNav automatically does this for IE since
34811      * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
34812      * handle keydown instead of keypress.
34813      */
34814     forceKeyDown: false,
34815     
34816     /**
34817      * Destroy this KeyNav (this is the same as calling disable).
34818      * @param {Boolean} removeEl True to remove the element associated with this KeyNav.
34819      */
34820     destroy: function(removeEl){
34821         this.map.destroy(removeEl);
34822         delete this.map;
34823     },
34824
34825     /**
34826      * Enable this KeyNav
34827      */
34828     enable: function() {
34829         this.map.enable();
34830         this.disabled = false;
34831     },
34832
34833     /**
34834      * Disable this KeyNav
34835      */
34836     disable: function() {
34837         this.map.disable();
34838         this.disabled = true;
34839     },
34840     
34841     /**
34842      * Convenience function for setting disabled/enabled by boolean.
34843      * @param {Boolean} disabled
34844      */
34845     setDisabled : function(disabled){
34846         this.map.setDisabled(disabled);
34847         this.disabled = disabled;
34848     },
34849     
34850     /**
34851      * Determines the event to bind to listen for keys. Depends on the {@link #forceKeyDown} setting,
34852      * as well as the useKeyDown option on the EventManager.
34853      * @return {String} The type of event to listen for.
34854      */
34855     getKeyEvent: function(forceKeyDown){
34856         return (forceKeyDown || Ext.EventManager.useKeyDown) ? 'keydown' : 'keypress';
34857     }
34858 });
34859
34860 /**
34861  * @class Ext.fx.Queue
34862  * Animation Queue mixin to handle chaining and queueing by target.
34863  * @private
34864  */
34865
34866 Ext.define('Ext.fx.Queue', {
34867
34868     requires: ['Ext.util.HashMap'],
34869
34870     constructor: function() {
34871         this.targets = Ext.create('Ext.util.HashMap');
34872         this.fxQueue = {};
34873     },
34874
34875     // @private
34876     getFxDefaults: function(targetId) {
34877         var target = this.targets.get(targetId);
34878         if (target) {
34879             return target.fxDefaults;
34880         }
34881         return {};
34882     },
34883
34884     // @private
34885     setFxDefaults: function(targetId, obj) {
34886         var target = this.targets.get(targetId);
34887         if (target) {
34888             target.fxDefaults = Ext.apply(target.fxDefaults || {}, obj);
34889         }
34890     },
34891
34892     // @private
34893     stopAnimation: function(targetId) {
34894         var me = this,
34895             queue = me.getFxQueue(targetId),
34896             ln = queue.length;
34897         while (ln) {
34898             queue[ln - 1].end();
34899             ln--;
34900         }
34901     },
34902
34903     /**
34904      * @private
34905      * Returns current animation object if the element has any effects actively running or queued, else returns false.
34906      */
34907     getActiveAnimation: function(targetId) {
34908         var queue = this.getFxQueue(targetId);
34909         return (queue && !!queue.length) ? queue[0] : false;
34910     },
34911
34912     // @private
34913     hasFxBlock: function(targetId) {
34914         var queue = this.getFxQueue(targetId);
34915         return queue && queue[0] && queue[0].block;
34916     },
34917
34918     // @private get fx queue for passed target, create if needed.
34919     getFxQueue: function(targetId) {
34920         if (!targetId) {
34921             return false;
34922         }
34923         var me = this,
34924             queue = me.fxQueue[targetId],
34925             target = me.targets.get(targetId);
34926
34927         if (!target) {
34928             return false;
34929         }
34930
34931         if (!queue) {
34932             me.fxQueue[targetId] = [];
34933             // GarbageCollector will need to clean up Elements since they aren't currently observable
34934             if (target.type != 'element') {
34935                 target.target.on('destroy', function() {
34936                     me.fxQueue[targetId] = [];
34937                 });
34938             }
34939         }
34940         return me.fxQueue[targetId];
34941     },
34942
34943     // @private
34944     queueFx: function(anim) {
34945         var me = this,
34946             target = anim.target,
34947             queue, ln;
34948
34949         if (!target) {
34950             return;
34951         }
34952
34953         queue = me.getFxQueue(target.getId());
34954         ln = queue.length;
34955
34956         if (ln) {
34957             if (anim.concurrent) {
34958                 anim.paused = false;
34959             }
34960             else {
34961                 queue[ln - 1].on('afteranimate', function() {
34962                     anim.paused = false;
34963                 });
34964             }
34965         }
34966         else {
34967             anim.paused = false;
34968         }
34969         anim.on('afteranimate', function() {
34970             Ext.Array.remove(queue, anim);
34971             if (anim.remove) {
34972                 if (target.type == 'element') {
34973                     var el = Ext.get(target.id);
34974                     if (el) {
34975                         el.remove();
34976                     }
34977                 }
34978             }
34979         }, this);
34980         queue.push(anim);
34981     }
34982 });
34983 /**
34984  * @class Ext.fx.target.Target
34985
34986 This class specifies a generic target for an animation. It provides a wrapper around a
34987 series of different types of objects to allow for a generic animation API.
34988 A target can be a single object or a Composite object containing other objects that are 
34989 to be animated. This class and it's subclasses are generally not created directly, the 
34990 underlying animation will create the appropriate Ext.fx.target.Target object by passing 
34991 the instance to be animated.
34992
34993 The following types of objects can be animated:
34994
34995 - {@link Ext.fx.target.Component Components}
34996 - {@link Ext.fx.target.Element Elements}
34997 - {@link Ext.fx.target.Sprite Sprites}
34998
34999  * @markdown
35000  * @abstract
35001  */
35002 Ext.define('Ext.fx.target.Target', {
35003
35004     isAnimTarget: true,
35005
35006     /**
35007      * Creates new Target.
35008      * @param {Ext.Component/Ext.Element/Ext.draw.Sprite} target The object to be animated
35009      */
35010     constructor: function(target) {
35011         this.target = target;
35012         this.id = this.getId();
35013     },
35014     
35015     getId: function() {
35016         return this.target.id;
35017     }
35018 });
35019
35020 /**
35021  * @class Ext.fx.target.Sprite
35022  * @extends Ext.fx.target.Target
35023
35024 This class represents a animation target for a {@link Ext.draw.Sprite}. In general this class will not be
35025 created directly, the {@link Ext.draw.Sprite} will be passed to the animation and
35026 and the appropriate target will be created.
35027
35028  * @markdown
35029  */
35030
35031 Ext.define('Ext.fx.target.Sprite', {
35032
35033     /* Begin Definitions */
35034
35035     extend: 'Ext.fx.target.Target',
35036
35037     /* End Definitions */
35038
35039     type: 'draw',
35040
35041     getFromPrim: function(sprite, attr) {
35042         var o;
35043         if (attr == 'translate') {
35044             o = {
35045                 x: sprite.attr.translation.x || 0,
35046                 y: sprite.attr.translation.y || 0
35047             };
35048         }
35049         else if (attr == 'rotate') {
35050             o = {
35051                 degrees: sprite.attr.rotation.degrees || 0,
35052                 x: sprite.attr.rotation.x,
35053                 y: sprite.attr.rotation.y
35054             };
35055         }
35056         else {
35057             o = sprite.attr[attr];
35058         }
35059         return o;
35060     },
35061
35062     getAttr: function(attr, val) {
35063         return [[this.target, val != undefined ? val : this.getFromPrim(this.target, attr)]];
35064     },
35065
35066     setAttr: function(targetData) {
35067         var ln = targetData.length,
35068             spriteArr = [],
35069             attrs, attr, attrArr, attPtr, spritePtr, idx, value, i, j, x, y, ln2;
35070         for (i = 0; i < ln; i++) {
35071             attrs = targetData[i].attrs;
35072             for (attr in attrs) {
35073                 attrArr = attrs[attr];
35074                 ln2 = attrArr.length;
35075                 for (j = 0; j < ln2; j++) {
35076                     spritePtr = attrArr[j][0];
35077                     attPtr = attrArr[j][1];
35078                     if (attr === 'translate') {
35079                         value = {
35080                             x: attPtr.x,
35081                             y: attPtr.y
35082                         };
35083                     }
35084                     else if (attr === 'rotate') {
35085                         x = attPtr.x;
35086                         if (isNaN(x)) {
35087                             x = null;
35088                         }
35089                         y = attPtr.y;
35090                         if (isNaN(y)) {
35091                             y = null;
35092                         }
35093                         value = {
35094                             degrees: attPtr.degrees,
35095                             x: x,
35096                             y: y
35097                         };
35098                     }
35099                     else if (attr === 'width' || attr === 'height' || attr === 'x' || attr === 'y') {
35100                         value = parseFloat(attPtr);
35101                     }
35102                     else {
35103                         value = attPtr;
35104                     }
35105                     idx = Ext.Array.indexOf(spriteArr, spritePtr);
35106                     if (idx == -1) {
35107                         spriteArr.push([spritePtr, {}]);
35108                         idx = spriteArr.length - 1;
35109                     }
35110                     spriteArr[idx][1][attr] = value;
35111                 }
35112             }
35113         }
35114         ln = spriteArr.length;
35115         for (i = 0; i < ln; i++) {
35116             spritePtr = spriteArr[i];
35117             spritePtr[0].setAttributes(spritePtr[1]);
35118         }
35119         this.target.redraw();
35120     }
35121 });
35122
35123 /**
35124  * @class Ext.fx.target.CompositeSprite
35125  * @extends Ext.fx.target.Sprite
35126
35127 This class represents a animation target for a {@link Ext.draw.CompositeSprite}. It allows
35128 each {@link Ext.draw.Sprite} in the group to be animated as a whole. In general this class will not be
35129 created directly, the {@link Ext.draw.CompositeSprite} will be passed to the animation and
35130 and the appropriate target will be created.
35131
35132  * @markdown
35133  */
35134
35135 Ext.define('Ext.fx.target.CompositeSprite', {
35136
35137     /* Begin Definitions */
35138
35139     extend: 'Ext.fx.target.Sprite',
35140
35141     /* End Definitions */
35142
35143     getAttr: function(attr, val) {
35144         var out = [],
35145             target = this.target;
35146         target.each(function(sprite) {
35147             out.push([sprite, val != undefined ? val : this.getFromPrim(sprite, attr)]);
35148         }, this);
35149         return out;
35150     }
35151 });
35152
35153 /**
35154  * @class Ext.fx.target.Component
35155  * @extends Ext.fx.target.Target
35156  * 
35157  * This class represents a animation target for a {@link Ext.Component}. In general this class will not be
35158  * created directly, the {@link Ext.Component} will be passed to the animation and
35159  * and the appropriate target will be created.
35160  */
35161 Ext.define('Ext.fx.target.Component', {
35162
35163     /* Begin Definitions */
35164    
35165     extend: 'Ext.fx.target.Target',
35166     
35167     /* End Definitions */
35168
35169     type: 'component',
35170
35171     // Methods to call to retrieve unspecified "from" values from a target Component
35172     getPropMethod: {
35173         top: function() {
35174             return this.getPosition(true)[1];
35175         },
35176         left: function() {
35177             return this.getPosition(true)[0];
35178         },
35179         x: function() {
35180             return this.getPosition()[0];
35181         },
35182         y: function() {
35183             return this.getPosition()[1];
35184         },
35185         height: function() {
35186             return this.getHeight();
35187         },
35188         width: function() {
35189             return this.getWidth();
35190         },
35191         opacity: function() {
35192             return this.el.getStyle('opacity');
35193         }
35194     },
35195
35196     compMethod: {
35197         top: 'setPosition',
35198         left: 'setPosition',
35199         x: 'setPagePosition',
35200         y: 'setPagePosition',
35201         height: 'setSize',
35202         width: 'setSize',
35203         opacity: 'setOpacity'
35204     },
35205
35206     // Read the named attribute from the target Component. Use the defined getter for the attribute
35207     getAttr: function(attr, val) {
35208         return [[this.target, val !== undefined ? val : this.getPropMethod[attr].call(this.target)]];
35209     },
35210
35211     setAttr: function(targetData, isFirstFrame, isLastFrame) {
35212         var me = this,
35213             target = me.target,
35214             ln = targetData.length,
35215             attrs, attr, o, i, j, meth, targets, left, top, w, h;
35216         for (i = 0; i < ln; i++) {
35217             attrs = targetData[i].attrs;
35218             for (attr in attrs) {
35219                 targets = attrs[attr].length;
35220                 meth = {
35221                     setPosition: {},
35222                     setPagePosition: {},
35223                     setSize: {},
35224                     setOpacity: {}
35225                 };
35226                 for (j = 0; j < targets; j++) {
35227                     o = attrs[attr][j];
35228                     // We REALLY want a single function call, so push these down to merge them: eg
35229                     // meth.setPagePosition.target = <targetComponent>
35230                     // meth.setPagePosition['x'] = 100
35231                     // meth.setPagePosition['y'] = 100
35232                     meth[me.compMethod[attr]].target = o[0];
35233                     meth[me.compMethod[attr]][attr] = o[1];
35234                 }
35235                 if (meth.setPosition.target) {
35236                     o = meth.setPosition;
35237                     left = (o.left === undefined) ? undefined : parseInt(o.left, 10);
35238                     top = (o.top === undefined) ? undefined : parseInt(o.top, 10);
35239                     o.target.setPosition(left, top);
35240                 }
35241                 if (meth.setPagePosition.target) {
35242                     o = meth.setPagePosition;
35243                     o.target.setPagePosition(o.x, o.y);
35244                 }
35245                 if (meth.setSize.target && meth.setSize.target.el) {
35246                     o = meth.setSize;
35247                     // Dimensions not being animated MUST NOT be autosized. They must remain at current value.
35248                     w = (o.width === undefined) ? o.target.getWidth() : parseInt(o.width, 10);
35249                     h = (o.height === undefined) ? o.target.getHeight() : parseInt(o.height, 10);
35250
35251                     // Only set the size of the Component on the last frame, or if the animation was
35252                     // configured with dynamic: true.
35253                     // In other cases, we just set the target element size.
35254                     // This will result in either clipping if animating a reduction in size, or the revealing of
35255                     // the inner elements of the Component if animating an increase in size.
35256                     // Component's animate function initially resizes to the larger size before resizing the
35257                     // outer element to clip the contents.
35258                     if (isLastFrame || me.dynamic) {
35259                         o.target.componentLayout.childrenChanged = true;
35260
35261                         // Flag if we are being called by an animating layout: use setCalculatedSize
35262                         if (me.layoutAnimation) {
35263                             o.target.setCalculatedSize(w, h);
35264                         } else {
35265                             o.target.setSize(w, h);
35266                         }
35267                     }
35268                     else {
35269                         o.target.el.setSize(w, h);
35270                     }
35271                 }
35272                 if (meth.setOpacity.target) {
35273                     o = meth.setOpacity;
35274                     o.target.el.setStyle('opacity', o.opacity);
35275                 }
35276             }
35277         }
35278     }
35279 });
35280
35281 /**
35282  * @class Ext.fx.CubicBezier
35283  * @ignore
35284  */
35285 Ext.define('Ext.fx.CubicBezier', {
35286
35287     /* Begin Definitions */
35288
35289     singleton: true,
35290
35291     /* End Definitions */
35292
35293     cubicBezierAtTime: function(t, p1x, p1y, p2x, p2y, duration) {
35294         var cx = 3 * p1x,
35295             bx = 3 * (p2x - p1x) - cx,
35296             ax = 1 - cx - bx,
35297             cy = 3 * p1y,
35298             by = 3 * (p2y - p1y) - cy,
35299             ay = 1 - cy - by;
35300         function sampleCurveX(t) {
35301             return ((ax * t + bx) * t + cx) * t;
35302         }
35303         function solve(x, epsilon) {
35304             var t = solveCurveX(x, epsilon);
35305             return ((ay * t + by) * t + cy) * t;
35306         }
35307         function solveCurveX(x, epsilon) {
35308             var t0, t1, t2, x2, d2, i;
35309             for (t2 = x, i = 0; i < 8; i++) {
35310                 x2 = sampleCurveX(t2) - x;
35311                 if (Math.abs(x2) < epsilon) {
35312                     return t2;
35313                 }
35314                 d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
35315                 if (Math.abs(d2) < 1e-6) {
35316                     break;
35317                 }
35318                 t2 = t2 - x2 / d2;
35319             }
35320             t0 = 0;
35321             t1 = 1;
35322             t2 = x;
35323             if (t2 < t0) {
35324                 return t0;
35325             }
35326             if (t2 > t1) {
35327                 return t1;
35328             }
35329             while (t0 < t1) {
35330                 x2 = sampleCurveX(t2);
35331                 if (Math.abs(x2 - x) < epsilon) {
35332                     return t2;
35333                 }
35334                 if (x > x2) {
35335                     t0 = t2;
35336                 } else {
35337                     t1 = t2;
35338                 }
35339                 t2 = (t1 - t0) / 2 + t0;
35340             }
35341             return t2;
35342         }
35343         return solve(t, 1 / (200 * duration));
35344     },
35345
35346     cubicBezier: function(x1, y1, x2, y2) {
35347         var fn = function(pos) {
35348             return Ext.fx.CubicBezier.cubicBezierAtTime(pos, x1, y1, x2, y2, 1);
35349         };
35350         fn.toCSS3 = function() {
35351             return 'cubic-bezier(' + [x1, y1, x2, y2].join(',') + ')';
35352         };
35353         fn.reverse = function() {
35354             return Ext.fx.CubicBezier.cubicBezier(1 - x2, 1 - y2, 1 - x1, 1 - y1);
35355         };
35356         return fn;
35357     }
35358 });
35359 /**
35360  * Represents an RGB color and provides helper functions get
35361  * color components in HSL color space.
35362  */
35363 Ext.define('Ext.draw.Color', {
35364
35365     /* Begin Definitions */
35366
35367     /* End Definitions */
35368
35369     colorToHexRe: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
35370     rgbRe: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
35371     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*/,
35372
35373     /**
35374      * @cfg {Number} lightnessFactor
35375      *
35376      * The default factor to compute the lighter or darker color. Defaults to 0.2.
35377      */
35378     lightnessFactor: 0.2,
35379
35380     /**
35381      * Creates new Color.
35382      * @param {Number} red Red component (0..255)
35383      * @param {Number} green Green component (0..255)
35384      * @param {Number} blue Blue component (0..255)
35385      */
35386     constructor : function(red, green, blue) {
35387         var me = this,
35388             clamp = Ext.Number.constrain;
35389         me.r = clamp(red, 0, 255);
35390         me.g = clamp(green, 0, 255);
35391         me.b = clamp(blue, 0, 255);
35392     },
35393
35394     /**
35395      * Get the red component of the color, in the range 0..255.
35396      * @return {Number}
35397      */
35398     getRed: function() {
35399         return this.r;
35400     },
35401
35402     /**
35403      * Get the green component of the color, in the range 0..255.
35404      * @return {Number}
35405      */
35406     getGreen: function() {
35407         return this.g;
35408     },
35409
35410     /**
35411      * Get the blue component of the color, in the range 0..255.
35412      * @return {Number}
35413      */
35414     getBlue: function() {
35415         return this.b;
35416     },
35417
35418     /**
35419      * Get the RGB values.
35420      * @return {Number[]}
35421      */
35422     getRGB: function() {
35423         var me = this;
35424         return [me.r, me.g, me.b];
35425     },
35426
35427     /**
35428      * Get the equivalent HSL components of the color.
35429      * @return {Number[]}
35430      */
35431     getHSL: function() {
35432         var me = this,
35433             r = me.r / 255,
35434             g = me.g / 255,
35435             b = me.b / 255,
35436             max = Math.max(r, g, b),
35437             min = Math.min(r, g, b),
35438             delta = max - min,
35439             h,
35440             s = 0,
35441             l = 0.5 * (max + min);
35442
35443         // min==max means achromatic (hue is undefined)
35444         if (min != max) {
35445             s = (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
35446             if (r == max) {
35447                 h = 60 * (g - b) / delta;
35448             } else if (g == max) {
35449                 h = 120 + 60 * (b - r) / delta;
35450             } else {
35451                 h = 240 + 60 * (r - g) / delta;
35452             }
35453             if (h < 0) {
35454                 h += 360;
35455             }
35456             if (h >= 360) {
35457                 h -= 360;
35458             }
35459         }
35460         return [h, s, l];
35461     },
35462
35463     /**
35464      * Return a new color that is lighter than this color.
35465      * @param {Number} factor Lighter factor (0..1), default to 0.2
35466      * @return Ext.draw.Color
35467      */
35468     getLighter: function(factor) {
35469         var hsl = this.getHSL();
35470         factor = factor || this.lightnessFactor;
35471         hsl[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
35472         return this.fromHSL(hsl[0], hsl[1], hsl[2]);
35473     },
35474
35475     /**
35476      * Return a new color that is darker than this color.
35477      * @param {Number} factor Darker factor (0..1), default to 0.2
35478      * @return Ext.draw.Color
35479      */
35480     getDarker: function(factor) {
35481         factor = factor || this.lightnessFactor;
35482         return this.getLighter(-factor);
35483     },
35484
35485     /**
35486      * Return the color in the hex format, i.e. '#rrggbb'.
35487      * @return {String}
35488      */
35489     toString: function() {
35490         var me = this,
35491             round = Math.round,
35492             r = round(me.r).toString(16),
35493             g = round(me.g).toString(16),
35494             b = round(me.b).toString(16);
35495         r = (r.length == 1) ? '0' + r : r;
35496         g = (g.length == 1) ? '0' + g : g;
35497         b = (b.length == 1) ? '0' + b : b;
35498         return ['#', r, g, b].join('');
35499     },
35500
35501     /**
35502      * Convert a color to hexadecimal format.
35503      *
35504      * **Note:** This method is both static and instance.
35505      *
35506      * @param {String/String[]} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
35507      * Can also be an Array, in this case the function handles the first member.
35508      * @returns {String} The color in hexadecimal format.
35509      * @static
35510      */
35511     toHex: function(color) {
35512         if (Ext.isArray(color)) {
35513             color = color[0];
35514         }
35515         if (!Ext.isString(color)) {
35516             return '';
35517         }
35518         if (color.substr(0, 1) === '#') {
35519             return color;
35520         }
35521         var digits = this.colorToHexRe.exec(color);
35522
35523         if (Ext.isArray(digits)) {
35524             var red = parseInt(digits[2], 10),
35525                 green = parseInt(digits[3], 10),
35526                 blue = parseInt(digits[4], 10),
35527                 rgb = blue | (green << 8) | (red << 16);
35528             return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
35529         }
35530         else {
35531             return '';
35532         }
35533     },
35534
35535     /**
35536      * Parse the string and create a new color.
35537      *
35538      * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
35539      *
35540      * If the string is not recognized, an undefined will be returned instead.
35541      *
35542      * **Note:** This method is both static and instance.
35543      *
35544      * @param {String} str Color in string.
35545      * @returns Ext.draw.Color
35546      * @static
35547      */
35548     fromString: function(str) {
35549         var values, r, g, b,
35550             parse = parseInt;
35551
35552         if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
35553             values = str.match(this.hexRe);
35554             if (values) {
35555                 r = parse(values[1], 16) >> 0;
35556                 g = parse(values[2], 16) >> 0;
35557                 b = parse(values[3], 16) >> 0;
35558                 if (str.length == 4) {
35559                     r += (r * 16);
35560                     g += (g * 16);
35561                     b += (b * 16);
35562                 }
35563             }
35564         }
35565         else {
35566             values = str.match(this.rgbRe);
35567             if (values) {
35568                 r = values[1];
35569                 g = values[2];
35570                 b = values[3];
35571             }
35572         }
35573
35574         return (typeof r == 'undefined') ? undefined : Ext.create('Ext.draw.Color', r, g, b);
35575     },
35576
35577     /**
35578      * Returns the gray value (0 to 255) of the color.
35579      *
35580      * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
35581      *
35582      * @returns {Number}
35583      */
35584     getGrayscale: function() {
35585         // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
35586         return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
35587     },
35588
35589     /**
35590      * Create a new color based on the specified HSL values.
35591      *
35592      * **Note:** This method is both static and instance.
35593      *
35594      * @param {Number} h Hue component (0..359)
35595      * @param {Number} s Saturation component (0..1)
35596      * @param {Number} l Lightness component (0..1)
35597      * @returns Ext.draw.Color
35598      * @static
35599      */
35600     fromHSL: function(h, s, l) {
35601         var C, X, m, i, rgb = [],
35602             abs = Math.abs,
35603             floor = Math.floor;
35604
35605         if (s == 0 || h == null) {
35606             // achromatic
35607             rgb = [l, l, l];
35608         }
35609         else {
35610             // http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
35611             // C is the chroma
35612             // X is the second largest component
35613             // m is the lightness adjustment
35614             h /= 60;
35615             C = s * (1 - abs(2 * l - 1));
35616             X = C * (1 - abs(h - 2 * floor(h / 2) - 1));
35617             m = l - C / 2;
35618             switch (floor(h)) {
35619                 case 0:
35620                     rgb = [C, X, 0];
35621                     break;
35622                 case 1:
35623                     rgb = [X, C, 0];
35624                     break;
35625                 case 2:
35626                     rgb = [0, C, X];
35627                     break;
35628                 case 3:
35629                     rgb = [0, X, C];
35630                     break;
35631                 case 4:
35632                     rgb = [X, 0, C];
35633                     break;
35634                 case 5:
35635                     rgb = [C, 0, X];
35636                     break;
35637             }
35638             rgb = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
35639         }
35640         return Ext.create('Ext.draw.Color', rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
35641     }
35642 }, function() {
35643     var prototype = this.prototype;
35644
35645     //These functions are both static and instance. TODO: find a more elegant way of copying them
35646     this.addStatics({
35647         fromHSL: function() {
35648             return prototype.fromHSL.apply(prototype, arguments);
35649         },
35650         fromString: function() {
35651             return prototype.fromString.apply(prototype, arguments);
35652         },
35653         toHex: function() {
35654             return prototype.toHex.apply(prototype, arguments);
35655         }
35656     });
35657 });
35658
35659 /**
35660  * @class Ext.dd.StatusProxy
35661  * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair.  This is the
35662  * default drag proxy used by all Ext.dd components.
35663  */
35664 Ext.define('Ext.dd.StatusProxy', {
35665     animRepair: false,
35666
35667     /**
35668      * Creates new StatusProxy.
35669      * @param {Object} config (optional) Config object.
35670      */
35671     constructor: function(config){
35672         Ext.apply(this, config);
35673         this.id = this.id || Ext.id();
35674         this.proxy = Ext.createWidget('component', {
35675             floating: true,
35676             stateful: false,
35677             id: this.id,
35678             html: '<div class="' + Ext.baseCSSPrefix + 'dd-drop-icon"></div>' +
35679                   '<div class="' + Ext.baseCSSPrefix + 'dd-drag-ghost"></div>',
35680             cls: Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed,
35681             shadow: !config || config.shadow !== false,
35682             renderTo: document.body
35683         });
35684
35685         this.el = this.proxy.el;
35686         this.el.show();
35687         this.el.setVisibilityMode(Ext.Element.VISIBILITY);
35688         this.el.hide();
35689
35690         this.ghost = Ext.get(this.el.dom.childNodes[1]);
35691         this.dropStatus = this.dropNotAllowed;
35692     },
35693     /**
35694      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
35695      * The CSS class to apply to the status element when drop is allowed.
35696      */
35697     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
35698     /**
35699      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
35700      * The CSS class to apply to the status element when drop is not allowed.
35701      */
35702     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
35703
35704     /**
35705      * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
35706      * over the current target element.
35707      * @param {String} cssClass The css class for the new drop status indicator image
35708      */
35709     setStatus : function(cssClass){
35710         cssClass = cssClass || this.dropNotAllowed;
35711         if(this.dropStatus != cssClass){
35712             this.el.replaceCls(this.dropStatus, cssClass);
35713             this.dropStatus = cssClass;
35714         }
35715     },
35716
35717     /**
35718      * Resets the status indicator to the default dropNotAllowed value
35719      * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
35720      */
35721     reset : function(clearGhost){
35722         this.el.dom.className = Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed;
35723         this.dropStatus = this.dropNotAllowed;
35724         if(clearGhost){
35725             this.ghost.update("");
35726         }
35727     },
35728
35729     /**
35730      * Updates the contents of the ghost element
35731      * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
35732      * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
35733      */
35734     update : function(html){
35735         if(typeof html == "string"){
35736             this.ghost.update(html);
35737         }else{
35738             this.ghost.update("");
35739             html.style.margin = "0";
35740             this.ghost.dom.appendChild(html);
35741         }
35742         var el = this.ghost.dom.firstChild;
35743         if(el){
35744             Ext.fly(el).setStyle('float', 'none');
35745         }
35746     },
35747
35748     /**
35749      * Returns the underlying proxy {@link Ext.Layer}
35750      * @return {Ext.Layer} el
35751     */
35752     getEl : function(){
35753         return this.el;
35754     },
35755
35756     /**
35757      * Returns the ghost element
35758      * @return {Ext.Element} el
35759      */
35760     getGhost : function(){
35761         return this.ghost;
35762     },
35763
35764     /**
35765      * Hides the proxy
35766      * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
35767      */
35768     hide : function(clear) {
35769         this.proxy.hide();
35770         if (clear) {
35771             this.reset(true);
35772         }
35773     },
35774
35775     /**
35776      * Stops the repair animation if it's currently running
35777      */
35778     stop : function(){
35779         if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
35780             this.anim.stop();
35781         }
35782     },
35783
35784     /**
35785      * Displays this proxy
35786      */
35787     show : function() {
35788         this.proxy.show();
35789         this.proxy.toFront();
35790     },
35791
35792     /**
35793      * Force the Layer to sync its shadow and shim positions to the element
35794      */
35795     sync : function(){
35796         this.proxy.el.sync();
35797     },
35798
35799     /**
35800      * Causes the proxy to return to its position of origin via an animation.  Should be called after an
35801      * invalid drop operation by the item being dragged.
35802      * @param {Number[]} xy The XY position of the element ([x, y])
35803      * @param {Function} callback The function to call after the repair is complete.
35804      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
35805      */
35806     repair : function(xy, callback, scope){
35807         this.callback = callback;
35808         this.scope = scope;
35809         if (xy && this.animRepair !== false) {
35810             this.el.addCls(Ext.baseCSSPrefix + 'dd-drag-repair');
35811             this.el.hideUnders(true);
35812             this.anim = this.el.animate({
35813                 duration: this.repairDuration || 500,
35814                 easing: 'ease-out',
35815                 to: {
35816                     x: xy[0],
35817                     y: xy[1]
35818                 },
35819                 stopAnimation: true,
35820                 callback: this.afterRepair,
35821                 scope: this
35822             });
35823         } else {
35824             this.afterRepair();
35825         }
35826     },
35827
35828     // private
35829     afterRepair : function(){
35830         this.hide(true);
35831         if(typeof this.callback == "function"){
35832             this.callback.call(this.scope || this);
35833         }
35834         this.callback = null;
35835         this.scope = null;
35836     },
35837
35838     destroy: function(){
35839         Ext.destroy(this.ghost, this.proxy, this.el);
35840     }
35841 });
35842 /**
35843  * A custom drag proxy implementation specific to {@link Ext.panel.Panel}s. This class
35844  * is primarily used internally for the Panel's drag drop implementation, and
35845  * should never need to be created directly.
35846  * @private
35847  */
35848 Ext.define('Ext.panel.Proxy', {
35849
35850     alternateClassName: 'Ext.dd.PanelProxy',
35851
35852     /**
35853      * Creates new panel proxy.
35854      * @param {Ext.panel.Panel} panel The {@link Ext.panel.Panel} to proxy for
35855      * @param {Object} [config] Config object
35856      */
35857     constructor: function(panel, config){
35858         /**
35859          * @property panel
35860          * @type Ext.panel.Panel
35861          */
35862         this.panel = panel;
35863         this.id = this.panel.id +'-ddproxy';
35864         Ext.apply(this, config);
35865     },
35866
35867     /**
35868      * @cfg {Boolean} insertProxy
35869      * True to insert a placeholder proxy element while dragging the panel, false to drag with no proxy.
35870      * Most Panels are not absolute positioned and therefore we need to reserve this space.
35871      */
35872     insertProxy: true,
35873
35874     // private overrides
35875     setStatus: Ext.emptyFn,
35876     reset: Ext.emptyFn,
35877     update: Ext.emptyFn,
35878     stop: Ext.emptyFn,
35879     sync: Ext.emptyFn,
35880
35881     /**
35882      * Gets the proxy's element
35883      * @return {Ext.Element} The proxy's element
35884      */
35885     getEl: function(){
35886         return this.ghost.el;
35887     },
35888
35889     /**
35890      * Gets the proxy's ghost Panel
35891      * @return {Ext.panel.Panel} The proxy's ghost Panel
35892      */
35893     getGhost: function(){
35894         return this.ghost;
35895     },
35896
35897     /**
35898      * Gets the proxy element. This is the element that represents where the
35899      * Panel was before we started the drag operation.
35900      * @return {Ext.Element} The proxy's element
35901      */
35902     getProxy: function(){
35903         return this.proxy;
35904     },
35905
35906     /**
35907      * Hides the proxy
35908      */
35909     hide : function(){
35910         if (this.ghost) {
35911             if (this.proxy) {
35912                 this.proxy.remove();
35913                 delete this.proxy;
35914             }
35915
35916             // Unghost the Panel, do not move the Panel to where the ghost was
35917             this.panel.unghost(null, false);
35918             delete this.ghost;
35919         }
35920     },
35921
35922     /**
35923      * Shows the proxy
35924      */
35925     show: function(){
35926         if (!this.ghost) {
35927             var panelSize = this.panel.getSize();
35928             this.panel.el.setVisibilityMode(Ext.Element.DISPLAY);
35929             this.ghost = this.panel.ghost();
35930             if (this.insertProxy) {
35931                 // bc Panels aren't absolute positioned we need to take up the space
35932                 // of where the panel previously was
35933                 this.proxy = this.panel.el.insertSibling({cls: Ext.baseCSSPrefix + 'panel-dd-spacer'});
35934                 this.proxy.setSize(panelSize);
35935             }
35936         }
35937     },
35938
35939     // private
35940     repair: function(xy, callback, scope) {
35941         this.hide();
35942         if (typeof callback == "function") {
35943             callback.call(scope || this);
35944         }
35945     },
35946
35947     /**
35948      * Moves the proxy to a different position in the DOM.  This is typically
35949      * called while dragging the Panel to keep the proxy sync'd to the Panel's
35950      * location.
35951      * @param {HTMLElement} parentNode The proxy's parent DOM node
35952      * @param {HTMLElement} [before] The sibling node before which the
35953      * proxy should be inserted (defaults to the parent's last child if not
35954      * specified)
35955      */
35956     moveProxy : function(parentNode, before){
35957         if (this.proxy) {
35958             parentNode.insertBefore(this.proxy.dom, before);
35959         }
35960     }
35961 });
35962 /**
35963  * @class Ext.layout.component.AbstractDock
35964  * @extends Ext.layout.component.Component
35965  * @private
35966  * This ComponentLayout handles docking for Panels. It takes care of panels that are
35967  * part of a ContainerLayout that sets this Panel's size and Panels that are part of
35968  * an AutoContainerLayout in which this panel get his height based of the CSS or
35969  * or its content.
35970  */
35971
35972 Ext.define('Ext.layout.component.AbstractDock', {
35973
35974     /* Begin Definitions */
35975
35976     extend: 'Ext.layout.component.Component',
35977
35978     /* End Definitions */
35979
35980     type: 'dock',
35981
35982     /**
35983      * @private
35984      * @property autoSizing
35985      * @type Boolean
35986      * This flag is set to indicate this layout may have an autoHeight/autoWidth.
35987      */
35988     autoSizing: true,
35989
35990     beforeLayout: function() {
35991         var returnValue = this.callParent(arguments);
35992         if (returnValue !== false && (!this.initializedBorders || this.childrenChanged) && (!this.owner.border || this.owner.manageBodyBorders)) {
35993             this.handleItemBorders();
35994             this.initializedBorders = true;
35995         }
35996         return returnValue;
35997     },
35998     
35999     handleItemBorders: function() {
36000         var owner = this.owner,
36001             body = owner.body,
36002             docked = this.getLayoutItems(),
36003             borders = {
36004                 top: [],
36005                 right: [],
36006                 bottom: [],
36007                 left: []
36008             },
36009             oldBorders = this.borders,
36010             opposites = {
36011                 top: 'bottom',
36012                 right: 'left',
36013                 bottom: 'top',
36014                 left: 'right'
36015             },
36016             i, ln, item, dock, side;
36017
36018         for (i = 0, ln = docked.length; i < ln; i++) {
36019             item = docked[i];
36020             dock = item.dock;
36021             
36022             if (item.ignoreBorderManagement) {
36023                 continue;
36024             }
36025             
36026             if (!borders[dock].satisfied) {
36027                 borders[dock].push(item);
36028                 borders[dock].satisfied = true;
36029             }
36030             
36031             if (!borders.top.satisfied && opposites[dock] !== 'top') {
36032                 borders.top.push(item);
36033             }
36034             if (!borders.right.satisfied && opposites[dock] !== 'right') {
36035                 borders.right.push(item);
36036             }            
36037             if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
36038                 borders.bottom.push(item);
36039             }            
36040             if (!borders.left.satisfied && opposites[dock] !== 'left') {
36041                 borders.left.push(item);
36042             }
36043         }
36044
36045         if (oldBorders) {
36046             for (side in oldBorders) {
36047                 if (oldBorders.hasOwnProperty(side)) {
36048                     ln = oldBorders[side].length;
36049                     if (!owner.manageBodyBorders) {
36050                         for (i = 0; i < ln; i++) {
36051                             oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
36052                         }
36053                         if (!oldBorders[side].satisfied && !owner.bodyBorder) {
36054                             body.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
36055                         }                    
36056                     }
36057                     else if (oldBorders[side].satisfied) {
36058                         body.setStyle('border-' + side + '-width', '');
36059                     }
36060                 }
36061             }
36062         }
36063                 
36064         for (side in borders) {
36065             if (borders.hasOwnProperty(side)) {
36066                 ln = borders[side].length;
36067                 if (!owner.manageBodyBorders) {
36068                     for (i = 0; i < ln; i++) {
36069                         borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
36070                     }
36071                     if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
36072                         body.addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
36073                     }                    
36074                 }
36075                 else if (borders[side].satisfied) {
36076                     body.setStyle('border-' + side + '-width', '1px');
36077                 }
36078             }
36079         }
36080         
36081         this.borders = borders;
36082     },
36083     
36084     /**
36085      * @protected
36086      * @param {Ext.Component} owner The Panel that owns this DockLayout
36087      * @param {Ext.Element} target The target in which we are going to render the docked items
36088      * @param {Array} args The arguments passed to the ComponentLayout.layout method
36089      */
36090     onLayout: function(width, height) {
36091         if (this.onLayout_running) {
36092             return;
36093         }
36094         this.onLayout_running = true;
36095         var me = this,
36096             owner = me.owner,
36097             body = owner.body,
36098             layout = owner.layout,
36099             target = me.getTarget(),
36100             autoWidth = false,
36101             autoHeight = false,
36102             padding, border, frameSize;
36103
36104         // We start of by resetting all the layouts info
36105         var info = me.info = {
36106             boxes: [],
36107             size: {
36108                 width: width,
36109                 height: height
36110             },
36111             bodyBox: {}
36112         };
36113         // Clear isAutoDock flag
36114         delete layout.isAutoDock;
36115
36116         Ext.applyIf(info, me.getTargetInfo());
36117
36118         // We need to bind to the ownerCt whenever we do not have a user set height or width.
36119         if (owner && owner.ownerCt && owner.ownerCt.layout && owner.ownerCt.layout.isLayout) {
36120             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
36121                 owner.ownerCt.layout.bindToOwnerCtComponent = true;
36122             }
36123             else {
36124                 owner.ownerCt.layout.bindToOwnerCtComponent = false;
36125             }
36126         }
36127
36128         // Determine if we have an autoHeight or autoWidth.
36129         if (height == null || width == null) {
36130             padding = info.padding;
36131             border = info.border;
36132             frameSize = me.frameSize;
36133
36134             // Auto-everything, clear out any style height/width and read from css
36135             if ((height == null) && (width == null)) {
36136                 autoHeight = true;
36137                 autoWidth = true;
36138                 me.setTargetSize(null);
36139                 me.setBodyBox({width: null, height: null});
36140             }
36141             // Auto-height
36142             else if (height == null) {
36143                 autoHeight = true;
36144                 // Clear any sizing that we already set in a previous layout
36145                 me.setTargetSize(width);
36146                 me.setBodyBox({width: width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: null});
36147             // Auto-width
36148             }
36149             else {
36150                 autoWidth = true;
36151                 // Clear any sizing that we already set in a previous layout
36152                 me.setTargetSize(null, height);
36153                 me.setBodyBox({width: null, height: height - padding.top - padding.bottom - border.top - border.bottom - frameSize.top - frameSize.bottom});
36154             }
36155
36156             // Run the container
36157             if (layout && layout.isLayout) {
36158                 // Auto-Sized so have the container layout notify the component layout.
36159                 layout.bindToOwnerCtComponent = true;
36160                 // Set flag so we don't do a redundant container layout
36161                 layout.isAutoDock = layout.autoSize !== true;
36162                 layout.layout();
36163
36164                 // If this is an autosized container layout, then we must compensate for a
36165                 // body that is being autosized.  We do not want to adjust the body's size
36166                 // to accommodate the dock items, but rather we will want to adjust the
36167                 // target's size.
36168                 //
36169                 // This is necessary because, particularly in a Box layout, all child items
36170                 // are set with absolute dimensions that are not flexible to the size of its
36171                 // innerCt/target.  So once they are laid out, they are sized for good. By
36172                 // shrinking the body box to accommodate dock items, we're merely cutting off
36173                 // parts of the body.  Not good.  Instead, the target's size should expand
36174                 // to fit the dock items in.  This is valid because the target container is
36175                 // suppose to be autosized to fit everything accordingly.
36176                 info.autoSizedCtLayout = layout.autoSize === true;
36177                 info.autoHeight = autoHeight;
36178                 info.autoWidth = autoWidth;
36179             }
36180
36181             // The dockItems method will add all the top and bottom docked items height
36182             // to the info.panelSize height. That's why we have to call setSize after
36183             // we dock all the items to actually set the panel's width and height.
36184             // We have to do this because the panel body and docked items will be position
36185             // absolute which doesn't stretch the panel.
36186             me.dockItems();
36187             me.setTargetSize(info.size.width, info.size.height);
36188         }
36189         else {
36190             me.setTargetSize(width, height);
36191             me.dockItems();
36192         }
36193         me.callParent(arguments);
36194         this.onLayout_running = false;
36195     },
36196
36197     /**
36198      * @protected
36199      * This method will first update all the information about the docked items,
36200      * body dimensions and position, the panel's total size. It will then
36201      * set all these values on the docked items and panel body.
36202      * @param {Array} items Array containing all the docked items
36203      * @param {Boolean} autoBoxes Set this to true if the Panel is part of an
36204      * AutoContainerLayout
36205      */
36206     dockItems : function() {
36207         this.calculateDockBoxes();
36208
36209         // Both calculateAutoBoxes and calculateSizedBoxes are changing the
36210         // information about the body, panel size, and boxes for docked items
36211         // inside a property called info.
36212         var info = this.info,
36213             autoWidth = info.autoWidth,
36214             autoHeight = info.autoHeight,
36215             boxes = info.boxes,
36216             ln = boxes.length,
36217             dock, i, item;
36218
36219         // We are going to loop over all the boxes that were calculated
36220         // and set the position of each item the box belongs to.
36221         for (i = 0; i < ln; i++) {
36222             dock = boxes[i];
36223             item = dock.item;
36224             item.setPosition(dock.x, dock.y);
36225             if ((autoWidth || autoHeight) && item.layout && item.layout.isLayout) {
36226                 // Auto-Sized so have the container layout notify the component layout.
36227                 item.layout.bindToOwnerCtComponent = true;
36228             }
36229         }
36230
36231         // Don't adjust body width/height if the target is using an auto container layout.
36232         // But, we do want to adjust the body size if the container layout is auto sized.
36233         if (!info.autoSizedCtLayout) {
36234             if (autoWidth) {
36235                 info.bodyBox.width = null;
36236             }
36237             if (autoHeight) {
36238                 info.bodyBox.height = null;
36239             }
36240         }
36241
36242         // If the bodyBox has been adjusted because of the docked items
36243         // we will update the dimensions and position of the panel's body.
36244         this.setBodyBox(info.bodyBox);
36245     },
36246
36247     /**
36248      * @protected
36249      * This method will set up some initial information about the panel size and bodybox
36250      * and then loop over all the items you pass it to take care of stretching, aligning,
36251      * dock position and all calculations involved with adjusting the body box.
36252      * @param {Array} items Array containing all the docked items we have to layout
36253      */
36254     calculateDockBoxes : function() {
36255         if (this.calculateDockBoxes_running) {
36256             // [AbstractDock#calculateDockBoxes] attempted to run again while it was already running
36257             return;
36258         }
36259         this.calculateDockBoxes_running = true;
36260         // We want to use the Panel's el width, and the Panel's body height as the initial
36261         // size we are going to use in calculateDockBoxes. We also want to account for
36262         // the border of the panel.
36263         var me = this,
36264             target = me.getTarget(),
36265             items = me.getLayoutItems(),
36266             owner = me.owner,
36267             bodyEl = owner.body,
36268             info = me.info,
36269             autoWidth = info.autoWidth,
36270             autoHeight = info.autoHeight,
36271             size = info.size,
36272             ln = items.length,
36273             padding = info.padding,
36274             border = info.border,
36275             frameSize = me.frameSize,
36276             item, i, box, rect;
36277
36278         // If this Panel is inside an AutoContainerLayout, we will base all the calculations
36279         // around the height of the body and the width of the panel.
36280         if (autoHeight) {
36281             size.height = bodyEl.getHeight() + padding.top + border.top + padding.bottom + border.bottom + frameSize.top + frameSize.bottom;
36282         }
36283         else {
36284             size.height = target.getHeight();
36285         }
36286         if (autoWidth) {
36287             size.width = bodyEl.getWidth() + padding.left + border.left + padding.right + border.right + frameSize.left + frameSize.right;
36288         }
36289         else {
36290             size.width = target.getWidth();
36291         }
36292
36293         info.bodyBox = {
36294             x: padding.left + frameSize.left,
36295             y: padding.top + frameSize.top,
36296             width: size.width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right,
36297             height: size.height - border.top - padding.top - border.bottom - padding.bottom - frameSize.top - frameSize.bottom
36298         };
36299
36300         // Loop over all the docked items
36301         for (i = 0; i < ln; i++) {
36302             item = items[i];
36303             // The initBox method will take care of stretching and alignment
36304             // In some cases it will also layout the dock items to be able to
36305             // get a width or height measurement
36306             box = me.initBox(item);
36307
36308             if (autoHeight === true) {
36309                 box = me.adjustAutoBox(box, i);
36310             }
36311             else {
36312                 box = me.adjustSizedBox(box, i);
36313             }
36314
36315             // Save our box. This allows us to loop over all docked items and do all
36316             // calculations first. Then in one loop we will actually size and position
36317             // all the docked items that have changed.
36318             info.boxes.push(box);
36319         }
36320         this.calculateDockBoxes_running = false;
36321     },
36322
36323     /**
36324      * @protected
36325      * This method will adjust the position of the docked item and adjust the body box
36326      * accordingly.
36327      * @param {Object} box The box containing information about the width and height
36328      * of this docked item
36329      * @param {Number} index The index position of this docked item
36330      * @return {Object} The adjusted box
36331      */
36332     adjustSizedBox : function(box, index) {
36333         var bodyBox = this.info.bodyBox,
36334             frameSize = this.frameSize,
36335             info = this.info,
36336             padding = info.padding,
36337             pos = box.type,
36338             border = info.border;
36339
36340         switch (pos) {
36341             case 'top':
36342                 box.y = bodyBox.y;
36343                 break;
36344
36345             case 'left':
36346                 box.x = bodyBox.x;
36347                 break;
36348
36349             case 'bottom':
36350                 box.y = (bodyBox.y + bodyBox.height) - box.height;
36351                 break;
36352
36353             case 'right':
36354                 box.x = (bodyBox.x + bodyBox.width) - box.width;
36355                 break;
36356         }
36357
36358         if (box.ignoreFrame) {
36359             if (pos == 'bottom') {
36360                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
36361             }
36362             else {
36363                 box.y -= (frameSize.top + padding.top + border.top);
36364             }
36365             if (pos == 'right') {
36366                 box.x += (frameSize.right + padding.right + border.right);
36367             }
36368             else {
36369                 box.x -= (frameSize.left + padding.left + border.left);
36370             }
36371         }
36372
36373         // If this is not an overlaying docked item, we have to adjust the body box
36374         if (!box.overlay) {
36375             switch (pos) {
36376                 case 'top':
36377                     bodyBox.y += box.height;
36378                     bodyBox.height -= box.height;
36379                     break;
36380
36381                 case 'left':
36382                     bodyBox.x += box.width;
36383                     bodyBox.width -= box.width;
36384                     break;
36385
36386                 case 'bottom':
36387                     bodyBox.height -= box.height;
36388                     break;
36389
36390                 case 'right':
36391                     bodyBox.width -= box.width;
36392                     break;
36393             }
36394         }
36395         return box;
36396     },
36397
36398     /**
36399      * @protected
36400      * This method will adjust the position of the docked item inside an AutoContainerLayout
36401      * and adjust the body box accordingly.
36402      * @param {Object} box The box containing information about the width and height
36403      * of this docked item
36404      * @param {Number} index The index position of this docked item
36405      * @return {Object} The adjusted box
36406      */
36407     adjustAutoBox : function (box, index) {
36408         var info = this.info,
36409             owner = this.owner,
36410             bodyBox = info.bodyBox,
36411             size = info.size,
36412             boxes = info.boxes,
36413             boxesLn = boxes.length,
36414             pos = box.type,
36415             frameSize = this.frameSize,
36416             padding = info.padding,
36417             border = info.border,
36418             autoSizedCtLayout = info.autoSizedCtLayout,
36419             ln = (boxesLn < index) ? boxesLn : index,
36420             i, adjustBox;
36421
36422         if (pos == 'top' || pos == 'bottom') {
36423             // This can affect the previously set left and right and bottom docked items
36424             for (i = 0; i < ln; i++) {
36425                 adjustBox = boxes[i];
36426                 if (adjustBox.stretched && adjustBox.type == 'left' || adjustBox.type == 'right') {
36427                     adjustBox.height += box.height;
36428                 }
36429                 else if (adjustBox.type == 'bottom') {
36430                     adjustBox.y += box.height;
36431                 }
36432             }
36433         }
36434
36435         switch (pos) {
36436             case 'top':
36437                 box.y = bodyBox.y;
36438                 if (!box.overlay) {
36439                     bodyBox.y += box.height;
36440                     if (info.autoHeight) {
36441                         size.height += box.height;
36442                     } else {
36443                         bodyBox.height -= box.height;
36444                     }
36445                 }
36446                 break;
36447
36448             case 'bottom':
36449                 if (!box.overlay) {
36450                     if (info.autoHeight) {
36451                         size.height += box.height;
36452                     } else {
36453                         bodyBox.height -= box.height;
36454                     }
36455                 }
36456                 box.y = (bodyBox.y + bodyBox.height);
36457                 break;
36458
36459             case 'left':
36460                 box.x = bodyBox.x;
36461                 if (!box.overlay) {
36462                     bodyBox.x += box.width;
36463                     if (info.autoWidth) {
36464                         size.width += box.width;
36465                     } else {
36466                         bodyBox.width -= box.width;
36467                     }
36468                 }
36469                 break;
36470
36471             case 'right':
36472                 if (!box.overlay) {
36473                     if (info.autoWidth) {
36474                         size.width += box.width;
36475                     } else {
36476                         bodyBox.width -= box.width;
36477                     }
36478                 }
36479                 box.x = (bodyBox.x + bodyBox.width);
36480                 break;
36481         }
36482
36483         if (box.ignoreFrame) {
36484             if (pos == 'bottom') {
36485                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
36486             }
36487             else {
36488                 box.y -= (frameSize.top + padding.top + border.top);
36489             }
36490             if (pos == 'right') {
36491                 box.x += (frameSize.right + padding.right + border.right);
36492             }
36493             else {
36494                 box.x -= (frameSize.left + padding.left + border.left);
36495             }
36496         }
36497         return box;
36498     },
36499
36500     /**
36501      * @protected
36502      * This method will create a box object, with a reference to the item, the type of dock
36503      * (top, left, bottom, right). It will also take care of stretching and aligning of the
36504      * docked items.
36505      * @param {Ext.Component} item The docked item we want to initialize the box for
36506      * @return {Object} The initial box containing width and height and other useful information
36507      */
36508     initBox : function(item) {
36509         var me = this,
36510             bodyBox = me.info.bodyBox,
36511             horizontal = (item.dock == 'top' || item.dock == 'bottom'),
36512             owner = me.owner,
36513             frameSize = me.frameSize,
36514             info = me.info,
36515             padding = info.padding,
36516             border = info.border,
36517             box = {
36518                 item: item,
36519                 overlay: item.overlay,
36520                 type: item.dock,
36521                 offsets: Ext.Element.parseBox(item.offsets || {}),
36522                 ignoreFrame: item.ignoreParentFrame
36523             };
36524         // First we are going to take care of stretch and align properties for all four dock scenarios.
36525         if (item.stretch !== false) {
36526             box.stretched = true;
36527             if (horizontal) {
36528                 box.x = bodyBox.x + box.offsets.left;
36529                 box.width = bodyBox.width - (box.offsets.left + box.offsets.right);
36530                 if (box.ignoreFrame) {
36531                     box.width += (frameSize.left + frameSize.right + border.left + border.right + padding.left + padding.right);
36532                 }
36533                 item.setCalculatedSize(box.width - item.el.getMargin('lr'), undefined, owner);
36534             }
36535             else {
36536                 box.y = bodyBox.y + box.offsets.top;
36537                 box.height = bodyBox.height - (box.offsets.bottom + box.offsets.top);
36538                 if (box.ignoreFrame) {
36539                     box.height += (frameSize.top + frameSize.bottom + border.top + border.bottom + padding.top + padding.bottom);
36540                 }
36541                 item.setCalculatedSize(undefined, box.height - item.el.getMargin('tb'), owner);
36542
36543                 // At this point IE will report the left/right-docked toolbar as having a width equal to the
36544                 // container's full width. Forcing a repaint kicks it into shape so it reports the correct width.
36545                 if (!Ext.supports.ComputedStyle) {
36546                     item.el.repaint();
36547                 }
36548             }
36549         }
36550         else {
36551             item.doComponentLayout();
36552             box.width = item.getWidth() - (box.offsets.left + box.offsets.right);
36553             box.height = item.getHeight() - (box.offsets.bottom + box.offsets.top);
36554             box.y += box.offsets.top;
36555             if (horizontal) {
36556                 box.x = (item.align == 'right') ? bodyBox.width - box.width : bodyBox.x;
36557                 box.x += box.offsets.left;
36558             }
36559         }
36560
36561         // If we haven't calculated the width or height of the docked item yet
36562         // do so, since we need this for our upcoming calculations
36563         if (box.width === undefined) {
36564             box.width = item.getWidth() + item.el.getMargin('lr');
36565         }
36566         if (box.height === undefined) {
36567             box.height = item.getHeight() + item.el.getMargin('tb');
36568         }
36569
36570         return box;
36571     },
36572
36573     /**
36574      * @protected
36575      * Returns an array containing all the <b>visible</b> docked items inside this layout's owner Panel
36576      * @return {Array} An array containing all the <b>visible</b> docked items of the Panel
36577      */
36578     getLayoutItems : function() {
36579         var it = this.owner.getDockedItems(),
36580             ln = it.length,
36581             i = 0,
36582             result = [];
36583         for (; i < ln; i++) {
36584             if (it[i].isVisible(true)) {
36585                 result.push(it[i]);
36586             }
36587         }
36588         return result;
36589     },
36590
36591     /**
36592      * @protected
36593      * Render the top and left docked items before any existing DOM nodes in our render target,
36594      * and then render the right and bottom docked items after. This is important, for such things
36595      * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
36596      * Our collection of docked items will already be ordered via Panel.getDockedItems().
36597      */
36598     renderItems: function(items, target) {
36599         var cns = target.dom.childNodes,
36600             cnsLn = cns.length,
36601             ln = items.length,
36602             domLn = 0,
36603             i, j, cn, item;
36604
36605         // Calculate the number of DOM nodes in our target that are not our docked items
36606         for (i = 0; i < cnsLn; i++) {
36607             cn = Ext.get(cns[i]);
36608             for (j = 0; j < ln; j++) {
36609                 item = items[j];
36610                 if (item.rendered && (cn.id == item.el.id || cn.contains(item.el.id))) {
36611                     break;
36612                 }
36613             }
36614
36615             if (j === ln) {
36616                 domLn++;
36617             }
36618         }
36619
36620         // Now we go through our docked items and render/move them
36621         for (i = 0, j = 0; i < ln; i++, j++) {
36622             item = items[i];
36623
36624             // If we're now at the right/bottom docked item, we jump ahead in our
36625             // DOM position, just past the existing DOM nodes.
36626             //
36627             // TODO: This is affected if users provide custom weight values to their
36628             // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
36629             // sort operation here, for now, in the name of performance. getDockedItems()
36630             // needs the sort operation not just for this layout-time rendering, but
36631             // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
36632             if (i === j && (item.dock === 'right' || item.dock === 'bottom')) {
36633                 j += domLn;
36634             }
36635
36636             // Same logic as Layout.renderItems()
36637             if (item && !item.rendered) {
36638                 this.renderItem(item, target, j);
36639             }
36640             else if (!this.isValidParent(item, target, j)) {
36641                 this.moveItem(item, target, j);
36642             }
36643         }
36644     },
36645
36646     /**
36647      * @protected
36648      * This function will be called by the dockItems method. Since the body is positioned absolute,
36649      * we need to give it dimensions and a position so that it is in the middle surrounded by
36650      * docked items
36651      * @param {Object} box An object containing new x, y, width and height values for the
36652      * Panel's body
36653      */
36654     setBodyBox : function(box) {
36655         var me = this,
36656             owner = me.owner,
36657             body = owner.body,
36658             info = me.info,
36659             bodyMargin = info.bodyMargin,
36660             padding = info.padding,
36661             border = info.border,
36662             frameSize = me.frameSize;
36663         
36664         // Panel collapse effectively hides the Panel's body, so this is a no-op.
36665         if (owner.collapsed) {
36666             return;
36667         }
36668         
36669         if (Ext.isNumber(box.width)) {
36670             box.width -= bodyMargin.left + bodyMargin.right;
36671         }
36672         
36673         if (Ext.isNumber(box.height)) {
36674             box.height -= bodyMargin.top + bodyMargin.bottom;
36675         }
36676         
36677         me.setElementSize(body, box.width, box.height);
36678         if (Ext.isNumber(box.x)) {
36679             body.setLeft(box.x - padding.left - frameSize.left);
36680         }
36681         if (Ext.isNumber(box.y)) {
36682             body.setTop(box.y - padding.top - frameSize.top);
36683         }
36684     },
36685
36686     /**
36687      * @protected
36688      * We are overriding the Ext.layout.Layout configureItem method to also add a class that
36689      * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
36690      * An example of a class added to a dock: right item is x-docked-right
36691      * @param {Ext.Component} item The item we are configuring
36692      */
36693     configureItem : function(item, pos) {
36694         this.callParent(arguments);
36695         if (item.dock == 'top' || item.dock == 'bottom') {
36696             item.layoutManagedWidth = 1;
36697             item.layoutManagedHeight = 2;
36698         } else {
36699             item.layoutManagedWidth = 2;
36700             item.layoutManagedHeight = 1;
36701         }
36702         
36703         item.addCls(Ext.baseCSSPrefix + 'docked');
36704         item.addClsWithUI('docked-' + item.dock);
36705     },
36706
36707     afterRemove : function(item) {
36708         this.callParent(arguments);
36709         if (this.itemCls) {
36710             item.el.removeCls(this.itemCls + '-' + item.dock);
36711         }
36712         var dom = item.el.dom;
36713
36714         if (!item.destroying && dom) {
36715             dom.parentNode.removeChild(dom);
36716         }
36717         this.childrenChanged = true;
36718     }
36719 });
36720 /**
36721  * @class Ext.util.Memento
36722  * This class manages a set of captured properties from an object. These captured properties
36723  * can later be restored to an object.
36724  */
36725 Ext.define('Ext.util.Memento', function () {
36726
36727     function captureOne (src, target, prop) {
36728         src[prop] = target[prop];
36729     }
36730
36731     function removeOne (src, target, prop) {
36732         delete src[prop];
36733     }
36734
36735     function restoreOne (src, target, prop) {
36736         var value = src[prop];
36737         if (value || src.hasOwnProperty(prop)) {
36738             restoreValue(target, prop, value);
36739         }
36740     }
36741
36742     function restoreValue (target, prop, value) {
36743         if (Ext.isDefined(value)) {
36744             target[prop] = value;
36745         } else {
36746             delete target[prop];
36747         }
36748     }
36749
36750     function doMany (doOne, src, target, props) {
36751         if (src) {
36752             if (Ext.isArray(props)) {
36753                 Ext.each(props, function (prop) {
36754                     doOne(src, target, prop);
36755                 });
36756             } else {
36757                 doOne(src, target, props);
36758             }
36759         }
36760     }
36761
36762     return {
36763         /**
36764          * @property data
36765          * The collection of captured properties.
36766          * @private
36767          */
36768         data: null,
36769
36770         /**
36771          * @property target
36772          * The default target object for capture/restore (passed to the constructor).
36773          */
36774         target: null,
36775
36776         /**
36777          * Creates a new memento and optionally captures properties from the target object.
36778          * @param {Object} target The target from which to capture properties. If specified in the
36779          * constructor, this target becomes the default target for all other operations.
36780          * @param {String/String[]} props The property or array of properties to capture.
36781          */
36782         constructor: function (target, props) {
36783             if (target) {
36784                 this.target = target;
36785                 if (props) {
36786                     this.capture(props);
36787                 }
36788             }
36789         },
36790
36791         /**
36792          * Captures the specified properties from the target object in this memento.
36793          * @param {String/String[]} props The property or array of properties to capture.
36794          * @param {Object} target The object from which to capture properties.
36795          */
36796         capture: function (props, target) {
36797             doMany(captureOne, this.data || (this.data = {}), target || this.target, props);
36798         },
36799
36800         /**
36801          * Removes the specified properties from this memento. These properties will not be
36802          * restored later without re-capturing their values.
36803          * @param {String/String[]} props The property or array of properties to remove.
36804          */
36805         remove: function (props) {
36806             doMany(removeOne, this.data, null, props);
36807         },
36808
36809         /**
36810          * Restores the specified properties from this memento to the target object.
36811          * @param {String/String[]} props The property or array of properties to restore.
36812          * @param {Boolean} clear True to remove the restored properties from this memento or
36813          * false to keep them (default is true).
36814          * @param {Object} target The object to which to restore properties.
36815          */
36816         restore: function (props, clear, target) {
36817             doMany(restoreOne, this.data, target || this.target, props);
36818             if (clear !== false) {
36819                 this.remove(props);
36820             }
36821         },
36822
36823         /**
36824          * Restores all captured properties in this memento to the target object.
36825          * @param {Boolean} clear True to remove the restored properties from this memento or
36826          * false to keep them (default is true).
36827          * @param {Object} target The object to which to restore properties.
36828          */
36829         restoreAll: function (clear, target) {
36830             var me = this,
36831                 t = target || this.target;
36832
36833             Ext.Object.each(me.data, function (prop, value) {
36834                 restoreValue(t, prop, value);
36835             });
36836
36837             if (clear !== false) {
36838                 delete me.data;
36839             }
36840         }
36841     };
36842 }());
36843
36844 /**
36845  * @class Ext.app.EventBus
36846  * @private
36847  */
36848 Ext.define('Ext.app.EventBus', {
36849     requires: [
36850         'Ext.util.Event'
36851     ],
36852     mixins: {
36853         observable: 'Ext.util.Observable'
36854     },
36855
36856     constructor: function() {
36857         this.mixins.observable.constructor.call(this);
36858
36859         this.bus = {};
36860
36861         var me = this;
36862         Ext.override(Ext.Component, {
36863             fireEvent: function(ev) {
36864                 if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
36865                     return me.dispatch.call(me, ev, this, arguments);
36866                 }
36867                 return false;
36868             }
36869         });
36870     },
36871
36872     dispatch: function(ev, target, args) {
36873         var bus = this.bus,
36874             selectors = bus[ev],
36875             selector, controllers, id, events, event, i, ln;
36876
36877         if (selectors) {
36878             // Loop over all the selectors that are bound to this event
36879             for (selector in selectors) {
36880                 // Check if the target matches the selector
36881                 if (target.is(selector)) {
36882                     // Loop over all the controllers that are bound to this selector
36883                     controllers = selectors[selector];
36884                     for (id in controllers) {
36885                         // Loop over all the events that are bound to this selector on this controller
36886                         events = controllers[id];
36887                         for (i = 0, ln = events.length; i < ln; i++) {
36888                             event = events[i];
36889                             // Fire the event!
36890                             if (event.fire.apply(event, Array.prototype.slice.call(args, 1)) === false) {
36891                                 return false;
36892                             };
36893                         }
36894                     }
36895                 }
36896             }
36897         }
36898     },
36899
36900     control: function(selectors, listeners, controller) {
36901         var bus = this.bus,
36902             selector, fn;
36903
36904         if (Ext.isString(selectors)) {
36905             selector = selectors;
36906             selectors = {};
36907             selectors[selector] = listeners;
36908             this.control(selectors, null, controller);
36909             return;
36910         }
36911
36912         Ext.Object.each(selectors, function(selector, listeners) {
36913             Ext.Object.each(listeners, function(ev, listener) {
36914                 var options = {},
36915                     scope = controller,
36916                     event = Ext.create('Ext.util.Event', controller, ev);
36917
36918                 // Normalize the listener
36919                 if (Ext.isObject(listener)) {
36920                     options = listener;
36921                     listener = options.fn;
36922                     scope = options.scope || controller;
36923                     delete options.fn;
36924                     delete options.scope;
36925                 }
36926
36927                 event.addListener(listener, scope, options);
36928
36929                 // Create the bus tree if it is not there yet
36930                 bus[ev] = bus[ev] || {};
36931                 bus[ev][selector] = bus[ev][selector] || {};
36932                 bus[ev][selector][controller.id] = bus[ev][selector][controller.id] || [];
36933
36934                 // Push our listener in our bus
36935                 bus[ev][selector][controller.id].push(event);
36936             });
36937         });
36938     }
36939 });
36940 /**
36941  * @class Ext.data.Types
36942  * <p>This is a static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
36943  * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
36944  * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
36945  * of this class.</p>
36946  * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
36947  * each type definition must contain three properties:</p>
36948  * <div class="mdetail-params"><ul>
36949  * <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
36950  * to be stored in the Field. The function is passed the collowing parameters:
36951  * <div class="mdetail-params"><ul>
36952  * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
36953  * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
36954  * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
36955  * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
36956  * ({@link Ext.data.reader.Json JsonReader}), or an XML element.</div></li>
36957  * </ul></div></div></li>
36958  * <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>
36959  * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
36960  * </ul></div>
36961  * <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
36962  * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
36963  *<pre><code>
36964 // Add a new Field data type which stores a VELatLong object in the Record.
36965 Ext.data.Types.VELATLONG = {
36966     convert: function(v, data) {
36967         return new VELatLong(data.lat, data.long);
36968     },
36969     sortType: function(v) {
36970         return v.Latitude;  // When sorting, order by latitude
36971     },
36972     type: 'VELatLong'
36973 };
36974 </code></pre>
36975  * <p>Then, when declaring a Model, use: <pre><code>
36976 var types = Ext.data.Types; // allow shorthand type access
36977 Ext.define('Unit',
36978     extend: 'Ext.data.Model',
36979     fields: [
36980         { name: 'unitName', mapping: 'UnitName' },
36981         { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
36982         { name: 'latitude', mapping: 'lat', type: types.FLOAT },
36983         { name: 'longitude', mapping: 'long', type: types.FLOAT },
36984         { name: 'position', type: types.VELATLONG }
36985     ]
36986 });
36987 </code></pre>
36988  * @singleton
36989  */
36990 Ext.define('Ext.data.Types', {
36991     singleton: true,
36992     requires: ['Ext.data.SortTypes']
36993 }, function() {
36994     var st = Ext.data.SortTypes;
36995
36996     Ext.apply(Ext.data.Types, {
36997         /**
36998          * @property {RegExp} stripRe
36999          * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
37000          * This should be overridden for localization.
37001          */
37002         stripRe: /[\$,%]/g,
37003
37004         /**
37005          * @property {Object} AUTO
37006          * This data type means that no conversion is applied to the raw data before it is placed into a Record.
37007          */
37008         AUTO: {
37009             convert: function(v) {
37010                 return v;
37011             },
37012             sortType: st.none,
37013             type: 'auto'
37014         },
37015
37016         /**
37017          * @property {Object} STRING
37018          * This data type means that the raw data is converted into a String before it is placed into a Record.
37019          */
37020         STRING: {
37021             convert: function(v) {
37022                 var defaultValue = this.useNull ? null : '';
37023                 return (v === undefined || v === null) ? defaultValue : String(v);
37024             },
37025             sortType: st.asUCString,
37026             type: 'string'
37027         },
37028
37029         /**
37030          * @property {Object} INT
37031          * This data type means that the raw data is converted into an integer before it is placed into a Record.
37032          * <p>The synonym <code>INTEGER</code> is equivalent.</p>
37033          */
37034         INT: {
37035             convert: function(v) {
37036                 return v !== undefined && v !== null && v !== '' ?
37037                     parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
37038             },
37039             sortType: st.none,
37040             type: 'int'
37041         },
37042
37043         /**
37044          * @property {Object} FLOAT
37045          * This data type means that the raw data is converted into a number before it is placed into a Record.
37046          * <p>The synonym <code>NUMBER</code> is equivalent.</p>
37047          */
37048         FLOAT: {
37049             convert: function(v) {
37050                 return v !== undefined && v !== null && v !== '' ?
37051                     parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
37052             },
37053             sortType: st.none,
37054             type: 'float'
37055         },
37056
37057         /**
37058          * @property {Object} BOOL
37059          * <p>This data type means that the raw data is converted into a boolean before it is placed into
37060          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
37061          * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
37062          */
37063         BOOL: {
37064             convert: function(v) {
37065                 if (this.useNull && (v === undefined || v === null || v === '')) {
37066                     return null;
37067                 }
37068                 return v === true || v === 'true' || v == 1;
37069             },
37070             sortType: st.none,
37071             type: 'bool'
37072         },
37073
37074         /**
37075          * @property {Object} DATE
37076          * This data type means that the raw data is converted into a Date before it is placed into a Record.
37077          * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
37078          * being applied.
37079          */
37080         DATE: {
37081             convert: function(v) {
37082                 var df = this.dateFormat,
37083                     parsed;
37084
37085                 if (!v) {
37086                     return null;
37087                 }
37088                 if (Ext.isDate(v)) {
37089                     return v;
37090                 }
37091                 if (df) {
37092                     if (df == 'timestamp') {
37093                         return new Date(v*1000);
37094                     }
37095                     if (df == 'time') {
37096                         return new Date(parseInt(v, 10));
37097                     }
37098                     return Ext.Date.parse(v, df);
37099                 }
37100
37101                 parsed = Date.parse(v);
37102                 return parsed ? new Date(parsed) : null;
37103             },
37104             sortType: st.asDate,
37105             type: 'date'
37106         }
37107     });
37108
37109     Ext.apply(Ext.data.Types, {
37110         /**
37111          * @property {Object} BOOLEAN
37112          * <p>This data type means that the raw data is converted into a boolean before it is placed into
37113          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
37114          * <p>The synonym <code>BOOL</code> is equivalent.</p>
37115          */
37116         BOOLEAN: this.BOOL,
37117
37118         /**
37119          * @property {Object} INTEGER
37120          * This data type means that the raw data is converted into an integer before it is placed into a Record.
37121          * <p>The synonym <code>INT</code> is equivalent.</p>
37122          */
37123         INTEGER: this.INT,
37124
37125         /**
37126          * @property {Object} NUMBER
37127          * This data type means that the raw data is converted into a number before it is placed into a Record.
37128          * <p>The synonym <code>FLOAT</code> is equivalent.</p>
37129          */
37130         NUMBER: this.FLOAT
37131     });
37132 });
37133
37134 /**
37135  * @author Ed Spencer
37136  *
37137  * Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class that
37138  * extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a {@link
37139  * Ext.data.Model Model}. For example, we might set up a model like this:
37140  *
37141  *     Ext.define('User', {
37142  *         extend: 'Ext.data.Model',
37143  *         fields: [
37144  *             'name', 'email',
37145  *             {name: 'age', type: 'int'},
37146  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
37147  *         ]
37148  *     });
37149  *
37150  * Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a couple
37151  * of different formats here; if we only pass in the string name of the field (as with name and email), the field is set
37152  * up with the 'auto' type. It's as if we'd done this instead:
37153  *
37154  *     Ext.define('User', {
37155  *         extend: 'Ext.data.Model',
37156  *         fields: [
37157  *             {name: 'name', type: 'auto'},
37158  *             {name: 'email', type: 'auto'},
37159  *             {name: 'age', type: 'int'},
37160  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
37161  *         ]
37162  *     });
37163  *
37164  * # Types and conversion
37165  *
37166  * The {@link #type} is important - it's used to automatically convert data passed to the field into the correct format.
37167  * In our example above, the name and email fields used the 'auto' type and will just accept anything that is passed
37168  * into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.
37169  *
37170  * Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can do
37171  * this using a {@link #convert} function. Here, we're going to create a new field based on another:
37172  *
37173  *     Ext.define('User', {
37174  *         extend: 'Ext.data.Model',
37175  *         fields: [
37176  *             'name', 'email',
37177  *             {name: 'age', type: 'int'},
37178  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'},
37179  *
37180  *             {
37181  *                 name: 'firstName',
37182  *                 convert: function(value, record) {
37183  *                     var fullName  = record.get('name'),
37184  *                         splits    = fullName.split(" "),
37185  *                         firstName = splits[0];
37186  *
37187  *                     return firstName;
37188  *                 }
37189  *             }
37190  *         ]
37191  *     });
37192  *
37193  * Now when we create a new User, the firstName is populated automatically based on the name:
37194  *
37195  *     var ed = Ext.create('User', {name: 'Ed Spencer'});
37196  *
37197  *     console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
37198  *
37199  * In fact, if we log out all of the data inside ed, we'll see this:
37200  *
37201  *     console.log(ed.data);
37202  *
37203  *     //outputs this:
37204  *     {
37205  *         age: 0,
37206  *         email: "",
37207  *         firstName: "Ed",
37208  *         gender: "Unknown",
37209  *         name: "Ed Spencer"
37210  *     }
37211  *
37212  * The age field has been given a default of zero because we made it an int type. As an auto field, email has defaulted
37213  * to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown' so we see
37214  * that now. Let's correct that and satisfy ourselves that the types work as we expect:
37215  *
37216  *     ed.set('gender', 'Male');
37217  *     ed.get('gender'); //returns 'Male'
37218  *
37219  *     ed.set('age', 25.4);
37220  *     ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
37221  */
37222 Ext.define('Ext.data.Field', {
37223     requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
37224     alias: 'data.field',
37225     
37226     constructor : function(config) {
37227         if (Ext.isString(config)) {
37228             config = {name: config};
37229         }
37230         Ext.apply(this, config);
37231         
37232         var types = Ext.data.Types,
37233             st = this.sortType,
37234             t;
37235
37236         if (this.type) {
37237             if (Ext.isString(this.type)) {
37238                 this.type = types[this.type.toUpperCase()] || types.AUTO;
37239             }
37240         } else {
37241             this.type = types.AUTO;
37242         }
37243
37244         // named sortTypes are supported, here we look them up
37245         if (Ext.isString(st)) {
37246             this.sortType = Ext.data.SortTypes[st];
37247         } else if(Ext.isEmpty(st)) {
37248             this.sortType = this.type.sortType;
37249         }
37250
37251         if (!this.convert) {
37252             this.convert = this.type.convert;
37253         }
37254     },
37255     
37256     /**
37257      * @cfg {String} name
37258      *
37259      * The name by which the field is referenced within the Model. This is referenced by, for example, the `dataIndex`
37260      * property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
37261      *
37262      * Note: In the simplest case, if no properties other than `name` are required, a field definition may consist of
37263      * just a String for the field name.
37264      */
37265     
37266     /**
37267      * @cfg {String/Object} type
37268      *
37269      * The data type for automatic conversion from received data to the *stored* value if
37270      * `{@link Ext.data.Field#convert convert}` has not been specified. This may be specified as a string value.
37271      * Possible values are
37272      *
37273      * - auto (Default, implies no conversion)
37274      * - string
37275      * - int
37276      * - float
37277      * - boolean
37278      * - date
37279      *
37280      * This may also be specified by referencing a member of the {@link Ext.data.Types} class.
37281      *
37282      * Developers may create their own application-specific data types by defining new members of the {@link
37283      * Ext.data.Types} class.
37284      */
37285     
37286     /**
37287      * @cfg {Function} convert
37288      *
37289      * A function which converts the value provided by the Reader into an object that will be stored in the Model.
37290      * It is passed the following parameters:
37291      *
37292      * - **v** : Mixed
37293      *
37294      *   The data value as read by the Reader, if undefined will use the configured `{@link Ext.data.Field#defaultValue
37295      *   defaultValue}`.
37296      *
37297      * - **rec** : Ext.data.Model
37298      *
37299      *   The data object containing the Model as read so far by the Reader. Note that the Model may not be fully populated
37300      *   at this point as the fields are read in the order that they are defined in your
37301      *   {@link Ext.data.Model#fields fields} array.
37302      *
37303      * Example of convert functions:
37304      *
37305      *     function fullName(v, record){
37306      *         return record.name.last + ', ' + record.name.first;
37307      *     }
37308      *
37309      *     function location(v, record){
37310      *         return !record.city ? '' : (record.city + ', ' + record.state);
37311      *     }
37312      *
37313      *     Ext.define('Dude', {
37314      *         extend: 'Ext.data.Model',
37315      *         fields: [
37316      *             {name: 'fullname',  convert: fullName},
37317      *             {name: 'firstname', mapping: 'name.first'},
37318      *             {name: 'lastname',  mapping: 'name.last'},
37319      *             {name: 'city', defaultValue: 'homeless'},
37320      *             'state',
37321      *             {name: 'location',  convert: location}
37322      *         ]
37323      *     });
37324      *
37325      *     // create the data store
37326      *     var store = Ext.create('Ext.data.Store', {
37327      *         reader: {
37328      *             type: 'json',
37329      *             model: 'Dude',
37330      *             idProperty: 'key',
37331      *             root: 'daRoot',
37332      *             totalProperty: 'total'
37333      *         }
37334      *     });
37335      *
37336      *     var myData = [
37337      *         { key: 1,
37338      *           name: { first: 'Fat',    last:  'Albert' }
37339      *           // notice no city, state provided in data object
37340      *         },
37341      *         { key: 2,
37342      *           name: { first: 'Barney', last:  'Rubble' },
37343      *           city: 'Bedrock', state: 'Stoneridge'
37344      *         },
37345      *         { key: 3,
37346      *           name: { first: 'Cliff',  last:  'Claven' },
37347      *           city: 'Boston',  state: 'MA'
37348      *         }
37349      *     ];
37350      */
37351
37352     /**
37353      * @cfg {String} dateFormat
37354      *
37355      * Used when converting received data into a Date when the {@link #type} is specified as `"date"`.
37356      *
37357      * A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the value provided by
37358      * the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a javascript millisecond
37359      * timestamp. See {@link Ext.Date}.
37360      */
37361     dateFormat: null,
37362     
37363     /**
37364      * @cfg {Boolean} useNull
37365      *
37366      * Use when converting received data into a Number type (either int or float). If the value cannot be
37367      * parsed, null will be used if useNull is true, otherwise the value will be 0. Defaults to false.
37368      */
37369     useNull: false,
37370     
37371     /**
37372      * @cfg {Object} defaultValue
37373      *
37374      * The default value used **when a Model is being created by a {@link Ext.data.reader.Reader Reader}**
37375      * when the item referenced by the `{@link Ext.data.Field#mapping mapping}` does not exist in the data object
37376      * (i.e. undefined). Defaults to "".
37377      */
37378     defaultValue: "",
37379
37380     /**
37381      * @cfg {String/Number} mapping
37382      *
37383      * (Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation that is creating the
37384      * {@link Ext.data.Model Model} to extract the Field value from the data object. If the path expression is the same
37385      * as the field name, the mapping may be omitted.
37386      *
37387      * The form of the mapping expression depends on the Reader being used.
37388      *
37389      * - {@link Ext.data.reader.Json}
37390      *
37391      *   The mapping is a string containing the javascript expression to reference the data from an element of the data
37392      *   item's {@link Ext.data.reader.Json#root root} Array. Defaults to the field name.
37393      *
37394      * - {@link Ext.data.reader.Xml}
37395      *
37396      *   The mapping is an {@link Ext.DomQuery} path to the data item relative to the DOM element that represents the
37397      *   {@link Ext.data.reader.Xml#record record}. Defaults to the field name.
37398      *
37399      * - {@link Ext.data.reader.Array}
37400      *
37401      *   The mapping is a number indicating the Array index of the field's value. Defaults to the field specification's
37402      *   Array position.
37403      *
37404      * If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
37405      * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
37406      * return the desired data.
37407      */
37408     mapping: null,
37409
37410     /**
37411      * @cfg {Function} sortType
37412      *
37413      * A function which converts a Field's value to a comparable value in order to ensure correct sort ordering.
37414      * Predefined functions are provided in {@link Ext.data.SortTypes}. A custom sort example:
37415      *
37416      *     // current sort     after sort we want
37417      *     // +-+------+          +-+------+
37418      *     // |1|First |          |1|First |
37419      *     // |2|Last  |          |3|Second|
37420      *     // |3|Second|          |2|Last  |
37421      *     // +-+------+          +-+------+
37422      *
37423      *     sortType: function(value) {
37424      *        switch (value.toLowerCase()) // native toLowerCase():
37425      *        {
37426      *           case 'first': return 1;
37427      *           case 'second': return 2;
37428      *           default: return 3;
37429      *        }
37430      *     }
37431      */
37432     sortType : null,
37433
37434     /**
37435      * @cfg {String} sortDir
37436      *
37437      * Initial direction to sort (`"ASC"` or `"DESC"`). Defaults to `"ASC"`.
37438      */
37439     sortDir : "ASC",
37440
37441     /**
37442      * @cfg {Boolean} allowBlank
37443      * @private
37444      *
37445      * Used for validating a {@link Ext.data.Model model}. Defaults to true. An empty value here will cause
37446      * {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid} to evaluate to false.
37447      */
37448     allowBlank : true,
37449
37450     /**
37451      * @cfg {Boolean} persist
37452      *
37453      * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This will also exclude
37454      * the field from being written using a {@link Ext.data.writer.Writer}. This option is useful when model fields are
37455      * used to keep state on the client but do not need to be persisted to the server. Defaults to true.
37456      */
37457     persist: true
37458 });
37459
37460 /**
37461  * @class Ext.util.AbstractMixedCollection
37462  * @private
37463  */
37464 Ext.define('Ext.util.AbstractMixedCollection', {
37465     requires: ['Ext.util.Filter'],
37466
37467     mixins: {
37468         observable: 'Ext.util.Observable'
37469     },
37470
37471     constructor: function(allowFunctions, keyFn) {
37472         var me = this;
37473
37474         me.items = [];
37475         me.map = {};
37476         me.keys = [];
37477         me.length = 0;
37478
37479         me.addEvents(
37480             /**
37481              * @event clear
37482              * Fires when the collection is cleared.
37483              */
37484             'clear',
37485
37486             /**
37487              * @event add
37488              * Fires when an item is added to the collection.
37489              * @param {Number} index The index at which the item was added.
37490              * @param {Object} o The item added.
37491              * @param {String} key The key associated with the added item.
37492              */
37493             'add',
37494
37495             /**
37496              * @event replace
37497              * Fires when an item is replaced in the collection.
37498              * @param {String} key he key associated with the new added.
37499              * @param {Object} old The item being replaced.
37500              * @param {Object} new The new item.
37501              */
37502             'replace',
37503
37504             /**
37505              * @event remove
37506              * Fires when an item is removed from the collection.
37507              * @param {Object} o The item being removed.
37508              * @param {String} key (optional) The key associated with the removed item.
37509              */
37510             'remove'
37511         );
37512
37513         me.allowFunctions = allowFunctions === true;
37514
37515         if (keyFn) {
37516             me.getKey = keyFn;
37517         }
37518
37519         me.mixins.observable.constructor.call(me);
37520     },
37521
37522     /**
37523      * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
37524      * function should add function references to the collection. Defaults to
37525      * <tt>false</tt>.
37526      */
37527     allowFunctions : false,
37528
37529     /**
37530      * Adds an item to the collection. Fires the {@link #add} event when complete.
37531      * @param {String} key <p>The key to associate with the item, or the new item.</p>
37532      * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
37533      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
37534      * the MixedCollection will be able to <i>derive</i> the key for the new item.
37535      * In this case just pass the new item in this parameter.</p>
37536      * @param {Object} o The item to add.
37537      * @return {Object} The item added.
37538      */
37539     add : function(key, obj){
37540         var me = this,
37541             myObj = obj,
37542             myKey = key,
37543             old;
37544
37545         if (arguments.length == 1) {
37546             myObj = myKey;
37547             myKey = me.getKey(myObj);
37548         }
37549         if (typeof myKey != 'undefined' && myKey !== null) {
37550             old = me.map[myKey];
37551             if (typeof old != 'undefined') {
37552                 return me.replace(myKey, myObj);
37553             }
37554             me.map[myKey] = myObj;
37555         }
37556         me.length++;
37557         me.items.push(myObj);
37558         me.keys.push(myKey);
37559         me.fireEvent('add', me.length - 1, myObj, myKey);
37560         return myObj;
37561     },
37562
37563     /**
37564       * MixedCollection has a generic way to fetch keys if you implement getKey.  The default implementation
37565       * simply returns <b><code>item.id</code></b> but you can provide your own implementation
37566       * to return a different value as in the following examples:<pre><code>
37567 // normal way
37568 var mc = new Ext.util.MixedCollection();
37569 mc.add(someEl.dom.id, someEl);
37570 mc.add(otherEl.dom.id, otherEl);
37571 //and so on
37572
37573 // using getKey
37574 var mc = new Ext.util.MixedCollection();
37575 mc.getKey = function(el){
37576    return el.dom.id;
37577 };
37578 mc.add(someEl);
37579 mc.add(otherEl);
37580
37581 // or via the constructor
37582 var mc = new Ext.util.MixedCollection(false, function(el){
37583    return el.dom.id;
37584 });
37585 mc.add(someEl);
37586 mc.add(otherEl);
37587      * </code></pre>
37588      * @param {Object} item The item for which to find the key.
37589      * @return {Object} The key for the passed item.
37590      */
37591     getKey : function(o){
37592          return o.id;
37593     },
37594
37595     /**
37596      * Replaces an item in the collection. Fires the {@link #replace} event when complete.
37597      * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
37598      * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
37599      * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
37600      * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
37601      * with one having the same key value, then just pass the replacement item in this parameter.</p>
37602      * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
37603      * with that key.
37604      * @return {Object}  The new item.
37605      */
37606     replace : function(key, o){
37607         var me = this,
37608             old,
37609             index;
37610
37611         if (arguments.length == 1) {
37612             o = arguments[0];
37613             key = me.getKey(o);
37614         }
37615         old = me.map[key];
37616         if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
37617              return me.add(key, o);
37618         }
37619         index = me.indexOfKey(key);
37620         me.items[index] = o;
37621         me.map[key] = o;
37622         me.fireEvent('replace', key, old, o);
37623         return o;
37624     },
37625
37626     /**
37627      * Adds all elements of an Array or an Object to the collection.
37628      * @param {Object/Array} objs An Object containing properties which will be added
37629      * to the collection, or an Array of values, each of which are added to the collection.
37630      * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
37631      * has been set to <tt>true</tt>.
37632      */
37633     addAll : function(objs){
37634         var me = this,
37635             i = 0,
37636             args,
37637             len,
37638             key;
37639
37640         if (arguments.length > 1 || Ext.isArray(objs)) {
37641             args = arguments.length > 1 ? arguments : objs;
37642             for (len = args.length; i < len; i++) {
37643                 me.add(args[i]);
37644             }
37645         } else {
37646             for (key in objs) {
37647                 if (objs.hasOwnProperty(key)) {
37648                     if (me.allowFunctions || typeof objs[key] != 'function') {
37649                         me.add(key, objs[key]);
37650                     }
37651                 }
37652             }
37653         }
37654     },
37655
37656     /**
37657      * Executes the specified function once for every item in the collection, passing the following arguments:
37658      * <div class="mdetail-params"><ul>
37659      * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
37660      * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
37661      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
37662      * </ul></div>
37663      * The function should return a boolean value. Returning false from the function will stop the iteration.
37664      * @param {Function} fn The function to execute for each item.
37665      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
37666      */
37667     each : function(fn, scope){
37668         var items = [].concat(this.items), // each safe for removal
37669             i = 0,
37670             len = items.length,
37671             item;
37672
37673         for (; i < len; i++) {
37674             item = items[i];
37675             if (fn.call(scope || item, item, i, len) === false) {
37676                 break;
37677             }
37678         }
37679     },
37680
37681     /**
37682      * Executes the specified function once for every key in the collection, passing each
37683      * key, and its associated item as the first two parameters.
37684      * @param {Function} fn The function to execute for each item.
37685      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
37686      */
37687     eachKey : function(fn, scope){
37688         var keys = this.keys,
37689             items = this.items,
37690             i = 0,
37691             len = keys.length;
37692
37693         for (; i < len; i++) {
37694             fn.call(scope || window, keys[i], items[i], i, len);
37695         }
37696     },
37697
37698     /**
37699      * Returns the first item in the collection which elicits a true return value from the
37700      * passed selection function.
37701      * @param {Function} fn The selection function to execute for each item.
37702      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
37703      * @return {Object} The first item in the collection which returned true from the selection function, or null if none was found
37704      */
37705     findBy : function(fn, scope) {
37706         var keys = this.keys,
37707             items = this.items,
37708             i = 0,
37709             len = items.length;
37710
37711         for (; i < len; i++) {
37712             if (fn.call(scope || window, items[i], keys[i])) {
37713                 return items[i];
37714             }
37715         }
37716         return null;
37717     },
37718
37719     find : function() {
37720         if (Ext.isDefined(Ext.global.console)) {
37721             Ext.global.console.warn('Ext.util.MixedCollection: find has been deprecated. Use findBy instead.');
37722         }
37723         return this.findBy.apply(this, arguments);
37724     },
37725
37726     /**
37727      * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
37728      * @param {Number} index The index to insert the item at.
37729      * @param {String} key The key to associate with the new item, or the item itself.
37730      * @param {Object} o (optional) If the second parameter was a key, the new item.
37731      * @return {Object} The item inserted.
37732      */
37733     insert : function(index, key, obj){
37734         var me = this,
37735             myKey = key,
37736             myObj = obj;
37737
37738         if (arguments.length == 2) {
37739             myObj = myKey;
37740             myKey = me.getKey(myObj);
37741         }
37742         if (me.containsKey(myKey)) {
37743             me.suspendEvents();
37744             me.removeAtKey(myKey);
37745             me.resumeEvents();
37746         }
37747         if (index >= me.length) {
37748             return me.add(myKey, myObj);
37749         }
37750         me.length++;
37751         Ext.Array.splice(me.items, index, 0, myObj);
37752         if (typeof myKey != 'undefined' && myKey !== null) {
37753             me.map[myKey] = myObj;
37754         }
37755         Ext.Array.splice(me.keys, index, 0, myKey);
37756         me.fireEvent('add', index, myObj, myKey);
37757         return myObj;
37758     },
37759
37760     /**
37761      * Remove an item from the collection.
37762      * @param {Object} o The item to remove.
37763      * @return {Object} The item removed or false if no item was removed.
37764      */
37765     remove : function(o){
37766         return this.removeAt(this.indexOf(o));
37767     },
37768
37769     /**
37770      * Remove all items in the passed array from the collection.
37771      * @param {Array} items An array of items to be removed.
37772      * @return {Ext.util.MixedCollection} this object
37773      */
37774     removeAll : function(items){
37775         Ext.each(items || [], function(item) {
37776             this.remove(item);
37777         }, this);
37778
37779         return this;
37780     },
37781
37782     /**
37783      * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
37784      * @param {Number} index The index within the collection of the item to remove.
37785      * @return {Object} The item removed or false if no item was removed.
37786      */
37787     removeAt : function(index){
37788         var me = this,
37789             o,
37790             key;
37791
37792         if (index < me.length && index >= 0) {
37793             me.length--;
37794             o = me.items[index];
37795             Ext.Array.erase(me.items, index, 1);
37796             key = me.keys[index];
37797             if (typeof key != 'undefined') {
37798                 delete me.map[key];
37799             }
37800             Ext.Array.erase(me.keys, index, 1);
37801             me.fireEvent('remove', o, key);
37802             return o;
37803         }
37804         return false;
37805     },
37806
37807     /**
37808      * Removed an item associated with the passed key fom the collection.
37809      * @param {String} key The key of the item to remove.
37810      * @return {Object} The item removed or false if no item was removed.
37811      */
37812     removeAtKey : function(key){
37813         return this.removeAt(this.indexOfKey(key));
37814     },
37815
37816     /**
37817      * Returns the number of items in the collection.
37818      * @return {Number} the number of items in the collection.
37819      */
37820     getCount : function(){
37821         return this.length;
37822     },
37823
37824     /**
37825      * Returns index within the collection of the passed Object.
37826      * @param {Object} o The item to find the index of.
37827      * @return {Number} index of the item. Returns -1 if not found.
37828      */
37829     indexOf : function(o){
37830         return Ext.Array.indexOf(this.items, o);
37831     },
37832
37833     /**
37834      * Returns index within the collection of the passed key.
37835      * @param {String} key The key to find the index of.
37836      * @return {Number} index of the key.
37837      */
37838     indexOfKey : function(key){
37839         return Ext.Array.indexOf(this.keys, key);
37840     },
37841
37842     /**
37843      * Returns the item associated with the passed key OR index.
37844      * Key has priority over index.  This is the equivalent
37845      * of calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
37846      * @param {String/Number} key The key or index of the item.
37847      * @return {Object} If the item is found, returns the item.  If the item was not found, returns <tt>undefined</tt>.
37848      * If an item was found, but is a Class, returns <tt>null</tt>.
37849      */
37850     get : function(key) {
37851         var me = this,
37852             mk = me.map[key],
37853             item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
37854         return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
37855     },
37856
37857     /**
37858      * Returns the item at the specified index.
37859      * @param {Number} index The index of the item.
37860      * @return {Object} The item at the specified index.
37861      */
37862     getAt : function(index) {
37863         return this.items[index];
37864     },
37865
37866     /**
37867      * Returns the item associated with the passed key.
37868      * @param {String/Number} key The key of the item.
37869      * @return {Object} The item associated with the passed key.
37870      */
37871     getByKey : function(key) {
37872         return this.map[key];
37873     },
37874
37875     /**
37876      * Returns true if the collection contains the passed Object as an item.
37877      * @param {Object} o  The Object to look for in the collection.
37878      * @return {Boolean} True if the collection contains the Object as an item.
37879      */
37880     contains : function(o){
37881         return Ext.Array.contains(this.items, o);
37882     },
37883
37884     /**
37885      * Returns true if the collection contains the passed Object as a key.
37886      * @param {String} key The key to look for in the collection.
37887      * @return {Boolean} True if the collection contains the Object as a key.
37888      */
37889     containsKey : function(key){
37890         return typeof this.map[key] != 'undefined';
37891     },
37892
37893     /**
37894      * Removes all items from the collection.  Fires the {@link #clear} event when complete.
37895      */
37896     clear : function(){
37897         var me = this;
37898
37899         me.length = 0;
37900         me.items = [];
37901         me.keys = [];
37902         me.map = {};
37903         me.fireEvent('clear');
37904     },
37905
37906     /**
37907      * Returns the first item in the collection.
37908      * @return {Object} the first item in the collection..
37909      */
37910     first : function() {
37911         return this.items[0];
37912     },
37913
37914     /**
37915      * Returns the last item in the collection.
37916      * @return {Object} the last item in the collection..
37917      */
37918     last : function() {
37919         return this.items[this.length - 1];
37920     },
37921
37922     /**
37923      * Collects all of the values of the given property and returns their sum
37924      * @param {String} property The property to sum by
37925      * @param {String} [root] 'root' property to extract the first argument from. This is used mainly when
37926      * summing fields in records, where the fields are all stored inside the 'data' object
37927      * @param {Number} [start=0] The record index to start at
37928      * @param {Number} [end=-1] The record index to end at
37929      * @return {Number} The total
37930      */
37931     sum: function(property, root, start, end) {
37932         var values = this.extractValues(property, root),
37933             length = values.length,
37934             sum    = 0,
37935             i;
37936
37937         start = start || 0;
37938         end   = (end || end === 0) ? end : length - 1;
37939
37940         for (i = start; i <= end; i++) {
37941             sum += values[i];
37942         }
37943
37944         return sum;
37945     },
37946
37947     /**
37948      * Collects unique values of a particular property in this MixedCollection
37949      * @param {String} property The property to collect on
37950      * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
37951      * summing fields in records, where the fields are all stored inside the 'data' object
37952      * @param {Boolean} allowBlank (optional) Pass true to allow null, undefined or empty string values
37953      * @return {Array} The unique values
37954      */
37955     collect: function(property, root, allowNull) {
37956         var values = this.extractValues(property, root),
37957             length = values.length,
37958             hits   = {},
37959             unique = [],
37960             value, strValue, i;
37961
37962         for (i = 0; i < length; i++) {
37963             value = values[i];
37964             strValue = String(value);
37965
37966             if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
37967                 hits[strValue] = true;
37968                 unique.push(value);
37969             }
37970         }
37971
37972         return unique;
37973     },
37974
37975     /**
37976      * @private
37977      * Extracts all of the given property values from the items in the MC. Mainly used as a supporting method for
37978      * functions like sum and collect.
37979      * @param {String} property The property to extract
37980      * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
37981      * extracting field data from Model instances, where the fields are stored inside the 'data' object
37982      * @return {Array} The extracted values
37983      */
37984     extractValues: function(property, root) {
37985         var values = this.items;
37986
37987         if (root) {
37988             values = Ext.Array.pluck(values, root);
37989         }
37990
37991         return Ext.Array.pluck(values, property);
37992     },
37993
37994     /**
37995      * Returns a range of items in this collection
37996      * @param {Number} startIndex (optional) The starting index. Defaults to 0.
37997      * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
37998      * @return {Array} An array of items
37999      */
38000     getRange : function(start, end){
38001         var me = this,
38002             items = me.items,
38003             range = [],
38004             i;
38005
38006         if (items.length < 1) {
38007             return range;
38008         }
38009
38010         start = start || 0;
38011         end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
38012         if (start <= end) {
38013             for (i = start; i <= end; i++) {
38014                 range[range.length] = items[i];
38015             }
38016         } else {
38017             for (i = start; i >= end; i--) {
38018                 range[range.length] = items[i];
38019             }
38020         }
38021         return range;
38022     },
38023
38024     /**
38025      * <p>Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
38026      * property/value pair with optional parameters for substring matching and case sensitivity. See
38027      * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
38028      * MixedCollection can be easily filtered by property like this:</p>
38029 <pre><code>
38030 //create a simple store with a few people defined
38031 var people = new Ext.util.MixedCollection();
38032 people.addAll([
38033     {id: 1, age: 25, name: 'Ed'},
38034     {id: 2, age: 24, name: 'Tommy'},
38035     {id: 3, age: 24, name: 'Arne'},
38036     {id: 4, age: 26, name: 'Aaron'}
38037 ]);
38038
38039 //a new MixedCollection containing only the items where age == 24
38040 var middleAged = people.filter('age', 24);
38041 </code></pre>
38042      *
38043      *
38044      * @param {Ext.util.Filter[]/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
38045      * @param {String/RegExp} value Either string that the property values
38046      * should start with or a RegExp to test against the property
38047      * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
38048      * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
38049      * @return {Ext.util.MixedCollection} The new filtered collection
38050      */
38051     filter : function(property, value, anyMatch, caseSensitive) {
38052         var filters = [],
38053             filterFn;
38054
38055         //support for the simple case of filtering by property/value
38056         if (Ext.isString(property)) {
38057             filters.push(Ext.create('Ext.util.Filter', {
38058                 property     : property,
38059                 value        : value,
38060                 anyMatch     : anyMatch,
38061                 caseSensitive: caseSensitive
38062             }));
38063         } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
38064             filters = filters.concat(property);
38065         }
38066
38067         //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
38068         //so here we construct a function that combines these filters by ANDing them together
38069         filterFn = function(record) {
38070             var isMatch = true,
38071                 length = filters.length,
38072                 i;
38073
38074             for (i = 0; i < length; i++) {
38075                 var filter = filters[i],
38076                     fn     = filter.filterFn,
38077                     scope  = filter.scope;
38078
38079                 isMatch = isMatch && fn.call(scope, record);
38080             }
38081
38082             return isMatch;
38083         };
38084
38085         return this.filterBy(filterFn);
38086     },
38087
38088     /**
38089      * Filter by a function. Returns a <i>new</i> collection that has been filtered.
38090      * The passed function will be called with each object in the collection.
38091      * If the function returns true, the value is included otherwise it is filtered.
38092      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
38093      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
38094      * @return {Ext.util.MixedCollection} The new filtered collection
38095      */
38096     filterBy : function(fn, scope) {
38097         var me = this,
38098             newMC  = new this.self(),
38099             keys   = me.keys,
38100             items  = me.items,
38101             length = items.length,
38102             i;
38103
38104         newMC.getKey = me.getKey;
38105
38106         for (i = 0; i < length; i++) {
38107             if (fn.call(scope || me, items[i], keys[i])) {
38108                 newMC.add(keys[i], items[i]);
38109             }
38110         }
38111
38112         return newMC;
38113     },
38114
38115     /**
38116      * Finds the index of the first matching object in this collection by a specific property/value.
38117      * @param {String} property The name of a property on your objects.
38118      * @param {String/RegExp} value A string that the property values
38119      * should start with or a RegExp to test against the property.
38120      * @param {Number} [start=0] The index to start searching at.
38121      * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning.
38122      * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
38123      * @return {Number} The matched index or -1
38124      */
38125     findIndex : function(property, value, start, anyMatch, caseSensitive){
38126         if(Ext.isEmpty(value, false)){
38127             return -1;
38128         }
38129         value = this.createValueMatcher(value, anyMatch, caseSensitive);
38130         return this.findIndexBy(function(o){
38131             return o && value.test(o[property]);
38132         }, null, start);
38133     },
38134
38135     /**
38136      * Find the index of the first matching object in this collection by a function.
38137      * If the function returns <i>true</i> it is considered a match.
38138      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
38139      * @param {Object} [scope] The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
38140      * @param {Number} [start=0] The index to start searching at.
38141      * @return {Number} The matched index or -1
38142      */
38143     findIndexBy : function(fn, scope, start){
38144         var me = this,
38145             keys = me.keys,
38146             items = me.items,
38147             i = start || 0,
38148             len = items.length;
38149
38150         for (; i < len; i++) {
38151             if (fn.call(scope || me, items[i], keys[i])) {
38152                 return i;
38153             }
38154         }
38155         return -1;
38156     },
38157
38158     /**
38159      * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
38160      * and by Ext.data.Store#filter
38161      * @private
38162      * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
38163      * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
38164      * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
38165      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
38166      */
38167     createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
38168         if (!value.exec) { // not a regex
38169             var er = Ext.String.escapeRegex;
38170             value = String(value);
38171
38172             if (anyMatch === true) {
38173                 value = er(value);
38174             } else {
38175                 value = '^' + er(value);
38176                 if (exactMatch === true) {
38177                     value += '$';
38178                 }
38179             }
38180             value = new RegExp(value, caseSensitive ? '' : 'i');
38181         }
38182         return value;
38183     },
38184
38185     /**
38186      * Creates a shallow copy of this collection
38187      * @return {Ext.util.MixedCollection}
38188      */
38189     clone : function() {
38190         var me = this,
38191             copy = new this.self(),
38192             keys = me.keys,
38193             items = me.items,
38194             i = 0,
38195             len = items.length;
38196
38197         for(; i < len; i++){
38198             copy.add(keys[i], items[i]);
38199         }
38200         copy.getKey = me.getKey;
38201         return copy;
38202     }
38203 });
38204
38205 /**
38206  * @docauthor Tommy Maintz <tommy@sencha.com>
38207  *
38208  * 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}.
38209  *
38210  * **NOTE**: This mixin is mainly for internal use and most users should not need to use it directly. It
38211  * is more likely you will want to use one of the component classes that import this mixin, such as
38212  * {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
38213  */
38214 Ext.define("Ext.util.Sortable", {
38215     /**
38216      * @property {Boolean} isSortable
38217      * Flag denoting that this object is sortable. Always true.
38218      */
38219     isSortable: true,
38220
38221     /**
38222      * @property {String} defaultSortDirection
38223      * The default sort direction to use if one is not specified.
38224      */
38225     defaultSortDirection: "ASC",
38226
38227     requires: [
38228         'Ext.util.Sorter'
38229     ],
38230
38231     /**
38232      * @property {String} sortRoot
38233      * The property in each item that contains the data to sort.
38234      */
38235
38236     /**
38237      * Performs initialization of this mixin. Component classes using this mixin should call this method during their
38238      * own initialization.
38239      */
38240     initSortable: function() {
38241         var me = this,
38242             sorters = me.sorters;
38243
38244         /**
38245          * @property {Ext.util.MixedCollection} sorters
38246          * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
38247          */
38248         me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
38249             return item.id || item.property;
38250         });
38251
38252         if (sorters) {
38253             me.sorters.addAll(me.decodeSorters(sorters));
38254         }
38255     },
38256
38257     /**
38258      * Sorts the data in the Store by one or more of its properties. Example usage:
38259      *
38260      *     //sort by a single field
38261      *     myStore.sort('myField', 'DESC');
38262      *
38263      *     //sorting by multiple fields
38264      *     myStore.sort([
38265      *         {
38266      *             property : 'age',
38267      *             direction: 'ASC'
38268      *         },
38269      *         {
38270      *             property : 'name',
38271      *             direction: 'DESC'
38272      *         }
38273      *     ]);
38274      *
38275      * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
38276      * the actual sorting to its internal {@link Ext.util.MixedCollection}.
38277      *
38278      * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
38279      *
38280      *     store.sort('myField');
38281      *     store.sort('myField');
38282      *
38283      * Is equivalent to this code, because Store handles the toggling automatically:
38284      *
38285      *     store.sort('myField', 'ASC');
38286      *     store.sort('myField', 'DESC');
38287      *
38288      * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
38289      * {@link Ext.data.Model Model}, or an array of sorter configurations.
38290      * @param {String} direction The overall direction to sort the data by. Defaults to "ASC".
38291      * @return {Ext.util.Sorter[]}
38292      */
38293     sort: function(sorters, direction, where, doSort) {
38294         var me = this,
38295             sorter, sorterFn,
38296             newSorters;
38297
38298         if (Ext.isArray(sorters)) {
38299             doSort = where;
38300             where = direction;
38301             newSorters = sorters;
38302         }
38303         else if (Ext.isObject(sorters)) {
38304             doSort = where;
38305             where = direction;
38306             newSorters = [sorters];
38307         }
38308         else if (Ext.isString(sorters)) {
38309             sorter = me.sorters.get(sorters);
38310
38311             if (!sorter) {
38312                 sorter = {
38313                     property : sorters,
38314                     direction: direction
38315                 };
38316                 newSorters = [sorter];
38317             }
38318             else if (direction === undefined) {
38319                 sorter.toggle();
38320             }
38321             else {
38322                 sorter.setDirection(direction);
38323             }
38324         }
38325
38326         if (newSorters && newSorters.length) {
38327             newSorters = me.decodeSorters(newSorters);
38328             if (Ext.isString(where)) {
38329                 if (where === 'prepend') {
38330                     sorters = me.sorters.clone().items;
38331
38332                     me.sorters.clear();
38333                     me.sorters.addAll(newSorters);
38334                     me.sorters.addAll(sorters);
38335                 }
38336                 else {
38337                     me.sorters.addAll(newSorters);
38338                 }
38339             }
38340             else {
38341                 me.sorters.clear();
38342                 me.sorters.addAll(newSorters);
38343             }
38344         }
38345
38346         if (doSort !== false) {
38347             me.onBeforeSort(newSorters);
38348             
38349             sorters = me.sorters.items;
38350             if (sorters.length) {
38351                 //construct an amalgamated sorter function which combines all of the Sorters passed
38352                 sorterFn = function(r1, r2) {
38353                     var result = sorters[0].sort(r1, r2),
38354                         length = sorters.length,
38355                         i;
38356
38357                         //if we have more than one sorter, OR any additional sorter functions together
38358                         for (i = 1; i < length; i++) {
38359                             result = result || sorters[i].sort.call(this, r1, r2);
38360                         }
38361
38362                     return result;
38363                 };
38364
38365                 me.doSort(sorterFn);
38366             }
38367         }
38368
38369         return sorters;
38370     },
38371
38372     onBeforeSort: Ext.emptyFn,
38373
38374     /**
38375      * @private
38376      * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
38377      * @param {Object[]} sorters The sorters array
38378      * @return {Ext.util.Sorter[]} Array of Ext.util.Sorter objects
38379      */
38380     decodeSorters: function(sorters) {
38381         if (!Ext.isArray(sorters)) {
38382             if (sorters === undefined) {
38383                 sorters = [];
38384             } else {
38385                 sorters = [sorters];
38386             }
38387         }
38388
38389         var length = sorters.length,
38390             Sorter = Ext.util.Sorter,
38391             fields = this.model ? this.model.prototype.fields : null,
38392             field,
38393             config, i;
38394
38395         for (i = 0; i < length; i++) {
38396             config = sorters[i];
38397
38398             if (!(config instanceof Sorter)) {
38399                 if (Ext.isString(config)) {
38400                     config = {
38401                         property: config
38402                     };
38403                 }
38404
38405                 Ext.applyIf(config, {
38406                     root     : this.sortRoot,
38407                     direction: "ASC"
38408                 });
38409
38410                 //support for 3.x style sorters where a function can be defined as 'fn'
38411                 if (config.fn) {
38412                     config.sorterFn = config.fn;
38413                 }
38414
38415                 //support a function to be passed as a sorter definition
38416                 if (typeof config == 'function') {
38417                     config = {
38418                         sorterFn: config
38419                     };
38420                 }
38421
38422                 // ensure sortType gets pushed on if necessary
38423                 if (fields && !config.transform) {
38424                     field = fields.get(config.property);
38425                     config.transform = field ? field.sortType : undefined;
38426                 }
38427                 sorters[i] = Ext.create('Ext.util.Sorter', config);
38428             }
38429         }
38430
38431         return sorters;
38432     },
38433
38434     getSorters: function() {
38435         return this.sorters.items;
38436     }
38437 });
38438 /**
38439  * @class Ext.util.MixedCollection
38440  * <p>
38441  * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
38442  * must be unique, the same key cannot exist twice. This collection is ordered, items in the
38443  * collection can be accessed by index  or via the key. Newly added items are added to
38444  * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
38445  * is heavier and provides more functionality. Sample usage:
38446  * <pre><code>
38447 var coll = new Ext.util.MixedCollection();
38448 coll.add('key1', 'val1');
38449 coll.add('key2', 'val2');
38450 coll.add('key3', 'val3');
38451
38452 console.log(coll.get('key1')); // prints 'val1'
38453 console.log(coll.indexOfKey('key3')); // prints 2
38454  * </code></pre>
38455  *
38456  * <p>
38457  * The MixedCollection also has support for sorting and filtering of the values in the collection.
38458  * <pre><code>
38459 var coll = new Ext.util.MixedCollection();
38460 coll.add('key1', 100);
38461 coll.add('key2', -100);
38462 coll.add('key3', 17);
38463 coll.add('key4', 0);
38464 var biggerThanZero = coll.filterBy(function(value){
38465     return value > 0;
38466 });
38467 console.log(biggerThanZero.getCount()); // prints 2
38468  * </code></pre>
38469  * </p>
38470  */
38471 Ext.define('Ext.util.MixedCollection', {
38472     extend: 'Ext.util.AbstractMixedCollection',
38473     mixins: {
38474         sortable: 'Ext.util.Sortable'
38475     },
38476
38477     /**
38478      * Creates new MixedCollection.
38479      * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
38480      * function should add function references to the collection. Defaults to
38481      * <tt>false</tt>.
38482      * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
38483      * and return the key value for that item.  This is used when available to look up the key on items that
38484      * were passed without an explicit key parameter to a MixedCollection method.  Passing this parameter is
38485      * equivalent to providing an implementation for the {@link #getKey} method.
38486      */
38487     constructor: function() {
38488         var me = this;
38489         me.callParent(arguments);
38490         me.addEvents('sort');
38491         me.mixins.sortable.initSortable.call(me);
38492     },
38493
38494     doSort: function(sorterFn) {
38495         this.sortBy(sorterFn);
38496     },
38497
38498     /**
38499      * @private
38500      * Performs the actual sorting based on a direction and a sorting function. Internally,
38501      * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
38502      * the sorted array data back into this.items and this.keys
38503      * @param {String} property Property to sort by ('key', 'value', or 'index')
38504      * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
38505      * @param {Function} fn (optional) Comparison function that defines the sort order.
38506      * Defaults to sorting by numeric value.
38507      */
38508     _sort : function(property, dir, fn){
38509         var me = this,
38510             i, len,
38511             dsc   = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
38512
38513             //this is a temporary array used to apply the sorting function
38514             c     = [],
38515             keys  = me.keys,
38516             items = me.items;
38517
38518         //default to a simple sorter function if one is not provided
38519         fn = fn || function(a, b) {
38520             return a - b;
38521         };
38522
38523         //copy all the items into a temporary array, which we will sort
38524         for(i = 0, len = items.length; i < len; i++){
38525             c[c.length] = {
38526                 key  : keys[i],
38527                 value: items[i],
38528                 index: i
38529             };
38530         }
38531
38532         //sort the temporary array
38533         Ext.Array.sort(c, function(a, b){
38534             var v = fn(a[property], b[property]) * dsc;
38535             if(v === 0){
38536                 v = (a.index < b.index ? -1 : 1);
38537             }
38538             return v;
38539         });
38540
38541         //copy the temporary array back into the main this.items and this.keys objects
38542         for(i = 0, len = c.length; i < len; i++){
38543             items[i] = c[i].value;
38544             keys[i]  = c[i].key;
38545         }
38546
38547         me.fireEvent('sort', me);
38548     },
38549
38550     /**
38551      * Sorts the collection by a single sorter function
38552      * @param {Function} sorterFn The function to sort by
38553      */
38554     sortBy: function(sorterFn) {
38555         var me     = this,
38556             items  = me.items,
38557             keys   = me.keys,
38558             length = items.length,
38559             temp   = [],
38560             i;
38561
38562         //first we create a copy of the items array so that we can sort it
38563         for (i = 0; i < length; i++) {
38564             temp[i] = {
38565                 key  : keys[i],
38566                 value: items[i],
38567                 index: i
38568             };
38569         }
38570
38571         Ext.Array.sort(temp, function(a, b) {
38572             var v = sorterFn(a.value, b.value);
38573             if (v === 0) {
38574                 v = (a.index < b.index ? -1 : 1);
38575             }
38576
38577             return v;
38578         });
38579
38580         //copy the temporary array back into the main this.items and this.keys objects
38581         for (i = 0; i < length; i++) {
38582             items[i] = temp[i].value;
38583             keys[i]  = temp[i].key;
38584         }
38585         
38586         me.fireEvent('sort', me, items, keys);
38587     },
38588
38589     /**
38590      * Reorders each of the items based on a mapping from old index to new index. Internally this
38591      * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
38592      * @param {Object} mapping Mapping from old item index to new item index
38593      */
38594     reorder: function(mapping) {
38595         var me = this,
38596             items = me.items,
38597             index = 0,
38598             length = items.length,
38599             order = [],
38600             remaining = [],
38601             oldIndex;
38602
38603         me.suspendEvents();
38604
38605         //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
38606         for (oldIndex in mapping) {
38607             order[mapping[oldIndex]] = items[oldIndex];
38608         }
38609
38610         for (index = 0; index < length; index++) {
38611             if (mapping[index] == undefined) {
38612                 remaining.push(items[index]);
38613             }
38614         }
38615
38616         for (index = 0; index < length; index++) {
38617             if (order[index] == undefined) {
38618                 order[index] = remaining.shift();
38619             }
38620         }
38621
38622         me.clear();
38623         me.addAll(order);
38624
38625         me.resumeEvents();
38626         me.fireEvent('sort', me);
38627     },
38628
38629     /**
38630      * Sorts this collection by <b>key</b>s.
38631      * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
38632      * @param {Function} fn (optional) Comparison function that defines the sort order.
38633      * Defaults to sorting by case insensitive string.
38634      */
38635     sortByKey : function(dir, fn){
38636         this._sort('key', dir, fn || function(a, b){
38637             var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
38638             return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
38639         });
38640     }
38641 });
38642
38643 /**
38644  * @author Ed Spencer
38645  * @class Ext.data.Errors
38646  * @extends Ext.util.MixedCollection
38647  *
38648  * <p>Wraps a collection of validation error responses and provides convenient functions for
38649  * accessing and errors for specific fields.</p>
38650  *
38651  * <p>Usually this class does not need to be instantiated directly - instances are instead created
38652  * automatically when {@link Ext.data.Model#validate validate} on a model instance:</p>
38653  *
38654 <pre><code>
38655 //validate some existing model instance - in this case it returned 2 failures messages
38656 var errors = myModel.validate();
38657
38658 errors.isValid(); //false
38659
38660 errors.length; //2
38661 errors.getByField('name');  // [{field: 'name',  message: 'must be present'}]
38662 errors.getByField('title'); // [{field: 'title', message: 'is too short'}]
38663 </code></pre>
38664  */
38665 Ext.define('Ext.data.Errors', {
38666     extend: 'Ext.util.MixedCollection',
38667
38668     /**
38669      * Returns true if there are no errors in the collection
38670      * @return {Boolean}
38671      */
38672     isValid: function() {
38673         return this.length === 0;
38674     },
38675
38676     /**
38677      * Returns all of the errors for the given field
38678      * @param {String} fieldName The field to get errors for
38679      * @return {Object[]} All errors for the given field
38680      */
38681     getByField: function(fieldName) {
38682         var errors = [],
38683             error, field, i;
38684
38685         for (i = 0; i < this.length; i++) {
38686             error = this.items[i];
38687
38688             if (error.field == fieldName) {
38689                 errors.push(error);
38690             }
38691         }
38692
38693         return errors;
38694     }
38695 });
38696
38697 /**
38698  * @author Ed Spencer
38699  *
38700  * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
38701  * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
38702  * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
38703  * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
38704  * 
38705  *     Ext.create('Ext.data.Store', {
38706  *         model: 'User',
38707  *         proxy: {
38708  *             type: 'ajax',
38709  *             url : 'users.json',
38710  *             reader: {
38711  *                 type: 'json',
38712  *                 root: 'users'
38713  *             }
38714  *         },
38715  *     });
38716  *     
38717  * The above reader is configured to consume a JSON string that looks something like this:
38718  *  
38719  *     {
38720  *         "success": true,
38721  *         "users": [
38722  *             { "name": "User 1" },
38723  *             { "name": "User 2" }
38724  *         ]
38725  *     }
38726  * 
38727  *
38728  * # Loading Nested Data
38729  *
38730  * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.Association
38731  * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
38732  * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
38733  *
38734  *     Ext.define("User", {
38735  *         extend: 'Ext.data.Model',
38736  *         fields: [
38737  *             'id', 'name'
38738  *         ],
38739  *
38740  *         hasMany: {model: 'Order', name: 'orders'},
38741  *
38742  *         proxy: {
38743  *             type: 'rest',
38744  *             url : 'users.json',
38745  *             reader: {
38746  *                 type: 'json',
38747  *                 root: 'users'
38748  *             }
38749  *         }
38750  *     });
38751  *
38752  *     Ext.define("Order", {
38753  *         extend: 'Ext.data.Model',
38754  *         fields: [
38755  *             'id', 'total'
38756  *         ],
38757  *
38758  *         hasMany  : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
38759  *         belongsTo: 'User'
38760  *     });
38761  *
38762  *     Ext.define("OrderItem", {
38763  *         extend: 'Ext.data.Model',
38764  *         fields: [
38765  *             'id', 'price', 'quantity', 'order_id', 'product_id'
38766  *         ],
38767  *
38768  *         belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
38769  *     });
38770  *
38771  *     Ext.define("Product", {
38772  *         extend: 'Ext.data.Model',
38773  *         fields: [
38774  *             'id', 'name'
38775  *         ],
38776  *
38777  *         hasMany: 'OrderItem'
38778  *     });
38779  *
38780  * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
38781  * Finally, each OrderItem has a single Product. This allows us to consume data like this:
38782  *
38783  *     {
38784  *         "users": [
38785  *             {
38786  *                 "id": 123,
38787  *                 "name": "Ed",
38788  *                 "orders": [
38789  *                     {
38790  *                         "id": 50,
38791  *                         "total": 100,
38792  *                         "order_items": [
38793  *                             {
38794  *                                 "id"      : 20,
38795  *                                 "price"   : 40,
38796  *                                 "quantity": 2,
38797  *                                 "product" : {
38798  *                                     "id": 1000,
38799  *                                     "name": "MacBook Pro"
38800  *                                 }
38801  *                             },
38802  *                             {
38803  *                                 "id"      : 21,
38804  *                                 "price"   : 20,
38805  *                                 "quantity": 3,
38806  *                                 "product" : {
38807  *                                     "id": 1001,
38808  *                                     "name": "iPhone"
38809  *                                 }
38810  *                             }
38811  *                         ]
38812  *                     }
38813  *                 ]
38814  *             }
38815  *         ]
38816  *     }
38817  *
38818  * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
38819  * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
38820  * and finally the Product associated with each OrderItem. Now we can read the data and use it as follows:
38821  *
38822  *     var store = Ext.create('Ext.data.Store', {
38823  *         model: "User"
38824  *     });
38825  *
38826  *     store.load({
38827  *         callback: function() {
38828  *             //the user that was loaded
38829  *             var user = store.first();
38830  *
38831  *             console.log("Orders for " + user.get('name') + ":")
38832  *
38833  *             //iterate over the Orders for each User
38834  *             user.orders().each(function(order) {
38835  *                 console.log("Order ID: " + order.getId() + ", which contains items:");
38836  *
38837  *                 //iterate over the OrderItems for each Order
38838  *                 order.orderItems().each(function(orderItem) {
38839  *                     //we know that the Product data is already loaded, so we can use the synchronous getProduct
38840  *                     //usually, we would use the asynchronous version (see {@link Ext.data.BelongsToAssociation})
38841  *                     var product = orderItem.getProduct();
38842  *
38843  *                     console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
38844  *                 });
38845  *             });
38846  *         }
38847  *     });
38848  *
38849  * Running the code above results in the following:
38850  *
38851  *     Orders for Ed:
38852  *     Order ID: 50, which contains items:
38853  *     2 orders of MacBook Pro
38854  *     3 orders of iPhone
38855  */
38856 Ext.define('Ext.data.reader.Reader', {
38857     requires: ['Ext.data.ResultSet'],
38858     alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
38859     
38860     /**
38861      * @cfg {String} idProperty
38862      * Name of the property within a row object that contains a record identifier value. Defaults to The id of the
38863      * model. If an idProperty is explicitly specified it will override that of the one specified on the model
38864      */
38865
38866     /**
38867      * @cfg {String} totalProperty
38868      * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
38869      * the whole dataset is not passed in one go, but is being paged from the remote server. Defaults to total.
38870      */
38871     totalProperty: 'total',
38872
38873     /**
38874      * @cfg {String} successProperty
38875      * Name of the property from which to retrieve the success attribute. Defaults to success. See
38876      * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
38877      */
38878     successProperty: 'success',
38879
38880     /**
38881      * @cfg {String} root
38882      * The name of the property which contains the Array of row objects.  For JSON reader it's dot-separated list
38883      * of property names.  For XML reader it's a CSS selector.  For array reader it's not applicable.
38884      * 
38885      * By default the natural root of the data will be used.  The root Json array, the root XML element, or the array.
38886      *
38887      * The data packet value for this property should be an empty array to clear the data or show no data.
38888      */
38889     root: '',
38890     
38891     /**
38892      * @cfg {String} messageProperty
38893      * The name of the property which contains a response message. This property is optional.
38894      */
38895     
38896     /**
38897      * @cfg {Boolean} implicitIncludes
38898      * True to automatically parse models nested within other models in a response object. See the
38899      * Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
38900      */
38901     implicitIncludes: true,
38902     
38903     isReader: true,
38904     
38905     /**
38906      * Creates new Reader.
38907      * @param {Object} config (optional) Config object.
38908      */
38909     constructor: function(config) {
38910         var me = this;
38911         
38912         Ext.apply(me, config || {});
38913         me.fieldCount = 0;
38914         me.model = Ext.ModelManager.getModel(config.model);
38915         if (me.model) {
38916             me.buildExtractors();
38917         }
38918     },
38919
38920     /**
38921      * Sets a new model for the reader.
38922      * @private
38923      * @param {Object} model The model to set.
38924      * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
38925      */
38926     setModel: function(model, setOnProxy) {
38927         var me = this;
38928         
38929         me.model = Ext.ModelManager.getModel(model);
38930         me.buildExtractors(true);
38931         
38932         if (setOnProxy && me.proxy) {
38933             me.proxy.setModel(me.model, true);
38934         }
38935     },
38936
38937     /**
38938      * Reads the given response object. This method normalizes the different types of response object that may be passed
38939      * to it, before handing off the reading of records to the {@link #readRecords} function.
38940      * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
38941      * @return {Ext.data.ResultSet} The parsed ResultSet object
38942      */
38943     read: function(response) {
38944         var data = response;
38945         
38946         if (response && response.responseText) {
38947             data = this.getResponseData(response);
38948         }
38949         
38950         if (data) {
38951             return this.readRecords(data);
38952         } else {
38953             return this.nullResultSet;
38954         }
38955     },
38956
38957     /**
38958      * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
38959      * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
38960      * processing should not be needed.
38961      * @param {Object} data The raw data object
38962      * @return {Ext.data.ResultSet} A ResultSet object
38963      */
38964     readRecords: function(data) {
38965         var me  = this;
38966         
38967         /*
38968          * We check here whether the number of fields has changed since the last read.
38969          * This works around an issue when a Model is used for both a Tree and another
38970          * source, because the tree decorates the model with extra fields and it causes
38971          * issues because the readers aren't notified.
38972          */
38973         if (me.fieldCount !== me.getFields().length) {
38974             me.buildExtractors(true);
38975         }
38976         
38977         /**
38978          * @property {Object} rawData
38979          * The raw data object that was last passed to readRecords. Stored for further processing if needed
38980          */
38981         me.rawData = data;
38982
38983         data = me.getData(data);
38984
38985         // If we pass an array as the data, we dont use getRoot on the data.
38986         // Instead the root equals to the data.
38987         var root    = Ext.isArray(data) ? data : me.getRoot(data),
38988             success = true,
38989             recordCount = 0,
38990             total, value, records, message;
38991             
38992         if (root) {
38993             total = root.length;
38994         }
38995
38996         if (me.totalProperty) {
38997             value = parseInt(me.getTotal(data), 10);
38998             if (!isNaN(value)) {
38999                 total = value;
39000             }
39001         }
39002
39003         if (me.successProperty) {
39004             value = me.getSuccess(data);
39005             if (value === false || value === 'false') {
39006                 success = false;
39007             }
39008         }
39009         
39010         if (me.messageProperty) {
39011             message = me.getMessage(data);
39012         }
39013         
39014         if (root) {
39015             records = me.extractData(root);
39016             recordCount = records.length;
39017         } else {
39018             recordCount = 0;
39019             records = [];
39020         }
39021
39022         return Ext.create('Ext.data.ResultSet', {
39023             total  : total || recordCount,
39024             count  : recordCount,
39025             records: records,
39026             success: success,
39027             message: message
39028         });
39029     },
39030
39031     /**
39032      * Returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
39033      * @param {Object[]/Object} root from server response
39034      * @private
39035      */
39036     extractData : function(root) {
39037         var me = this,
39038             values  = [],
39039             records = [],
39040             Model   = me.model,
39041             i       = 0,
39042             length  = root.length,
39043             idProp  = me.getIdProperty(),
39044             node, id, record;
39045             
39046         if (!root.length && Ext.isObject(root)) {
39047             root = [root];
39048             length = 1;
39049         }
39050
39051         for (; i < length; i++) {
39052             node   = root[i];
39053             values = me.extractValues(node);
39054             id     = me.getId(node);
39055
39056             
39057             record = new Model(values, id, node);
39058             records.push(record);
39059                 
39060             if (me.implicitIncludes) {
39061                 me.readAssociated(record, node);
39062             }
39063         }
39064
39065         return records;
39066     },
39067     
39068     /**
39069      * @private
39070      * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
39071      * on the record provided.
39072      * @param {Ext.data.Model} record The record to load associations for
39073      * @param {Object} data The data object
39074      * @return {String} Return value description
39075      */
39076     readAssociated: function(record, data) {
39077         var associations = record.associations.items,
39078             i            = 0,
39079             length       = associations.length,
39080             association, associationData, proxy, reader;
39081         
39082         for (; i < length; i++) {
39083             association     = associations[i];
39084             associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
39085             
39086             if (associationData) {
39087                 reader = association.getReader();
39088                 if (!reader) {
39089                     proxy = association.associatedModel.proxy;
39090                     // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
39091                     if (proxy) {
39092                         reader = proxy.getReader();
39093                     } else {
39094                         reader = new this.constructor({
39095                             model: association.associatedName
39096                         });
39097                     }
39098                 }
39099                 association.read(record, reader, associationData);
39100             }  
39101         }
39102     },
39103     
39104     /**
39105      * @private
39106      * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
39107      * record, this should return the relevant part of that data for the given association name. This is only really
39108      * needed to support the XML Reader, which has to do a query to get the associated data object
39109      * @param {Object} data The raw data object
39110      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
39111      * @return {Object} The root
39112      */
39113     getAssociatedDataRoot: function(data, associationName) {
39114         return data[associationName];
39115     },
39116     
39117     getFields: function() {
39118         return this.model.prototype.fields.items;
39119     },
39120
39121     /**
39122      * @private
39123      * Given an object representing a single model instance's data, iterates over the model's fields and
39124      * builds an object with the value for each field.
39125      * @param {Object} data The data object to convert
39126      * @return {Object} Data object suitable for use with a model constructor
39127      */
39128     extractValues: function(data) {
39129         var fields = this.getFields(),
39130             i      = 0,
39131             length = fields.length,
39132             output = {},
39133             field, value;
39134
39135         for (; i < length; i++) {
39136             field = fields[i];
39137             value = this.extractorFunctions[i](data);
39138
39139             output[field.name] = value;
39140         }
39141
39142         return output;
39143     },
39144
39145     /**
39146      * @private
39147      * By default this function just returns what is passed to it. It can be overridden in a subclass
39148      * to return something else. See XmlReader for an example.
39149      * @param {Object} data The data object
39150      * @return {Object} The normalized data object
39151      */
39152     getData: function(data) {
39153         return data;
39154     },
39155
39156     /**
39157      * @private
39158      * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
39159      * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
39160      * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
39161      * @param {Object} data The data object
39162      * @return {Object} The same data object
39163      */
39164     getRoot: function(data) {
39165         return data;
39166     },
39167
39168     /**
39169      * Takes a raw response object (as passed to this.read) and returns the useful data segment of it. This must be
39170      * implemented by each subclass
39171      * @param {Object} response The responce object
39172      * @return {Object} The useful data from the response
39173      */
39174     getResponseData: function(response) {
39175         Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");
39176     },
39177
39178     /**
39179      * @private
39180      * Reconfigures the meta data tied to this Reader
39181      */
39182     onMetaChange : function(meta) {
39183         var fields = meta.fields,
39184             newModel;
39185         
39186         Ext.apply(this, meta);
39187         
39188         if (fields) {
39189             newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
39190                 extend: 'Ext.data.Model',
39191                 fields: fields
39192             });
39193             this.setModel(newModel, true);
39194         } else {
39195             this.buildExtractors(true);
39196         }
39197     },
39198     
39199     /**
39200      * Get the idProperty to use for extracting data
39201      * @private
39202      * @return {String} The id property
39203      */
39204     getIdProperty: function(){
39205         var prop = this.idProperty;
39206         if (Ext.isEmpty(prop)) {
39207             prop = this.model.prototype.idProperty;
39208         }
39209         return prop;
39210     },
39211
39212     /**
39213      * @private
39214      * This builds optimized functions for retrieving record data and meta data from an object.
39215      * Subclasses may need to implement their own getRoot function.
39216      * @param {Boolean} [force=false] True to automatically remove existing extractor functions first
39217      */
39218     buildExtractors: function(force) {
39219         var me          = this,
39220             idProp      = me.getIdProperty(),
39221             totalProp   = me.totalProperty,
39222             successProp = me.successProperty,
39223             messageProp = me.messageProperty,
39224             accessor;
39225             
39226         if (force === true) {
39227             delete me.extractorFunctions;
39228         }
39229         
39230         if (me.extractorFunctions) {
39231             return;
39232         }   
39233
39234         //build the extractors for all the meta data
39235         if (totalProp) {
39236             me.getTotal = me.createAccessor(totalProp);
39237         }
39238
39239         if (successProp) {
39240             me.getSuccess = me.createAccessor(successProp);
39241         }
39242
39243         if (messageProp) {
39244             me.getMessage = me.createAccessor(messageProp);
39245         }
39246
39247         if (idProp) {
39248             accessor = me.createAccessor(idProp);
39249
39250             me.getId = function(record) {
39251                 var id = accessor.call(me, record);
39252                 return (id === undefined || id === '') ? null : id;
39253             };
39254         } else {
39255             me.getId = function() {
39256                 return null;
39257             };
39258         }
39259         me.buildFieldExtractors();
39260     },
39261
39262     /**
39263      * @private
39264      */
39265     buildFieldExtractors: function() {
39266         //now build the extractors for all the fields
39267         var me = this,
39268             fields = me.getFields(),
39269             ln = fields.length,
39270             i  = 0,
39271             extractorFunctions = [],
39272             field, map;
39273
39274         for (; i < ln; i++) {
39275             field = fields[i];
39276             map   = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
39277
39278             extractorFunctions.push(me.createAccessor(map));
39279         }
39280         me.fieldCount = ln;
39281
39282         me.extractorFunctions = extractorFunctions;
39283     }
39284 }, function() {
39285     Ext.apply(this, {
39286         // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
39287         nullResultSet: Ext.create('Ext.data.ResultSet', {
39288             total  : 0,
39289             count  : 0,
39290             records: [],
39291             success: true
39292         })
39293     });
39294 });
39295 /**
39296  * @author Ed Spencer
39297  * @class Ext.data.reader.Json
39298  * @extends Ext.data.reader.Reader
39299  *
39300  * <p>The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
39301  * happens as a result of loading a Store - for example we might create something like this:</p>
39302  *
39303 <pre><code>
39304 Ext.define('User', {
39305     extend: 'Ext.data.Model',
39306     fields: ['id', 'name', 'email']
39307 });
39308
39309 var store = Ext.create('Ext.data.Store', {
39310     model: 'User',
39311     proxy: {
39312         type: 'ajax',
39313         url : 'users.json',
39314         reader: {
39315             type: 'json'
39316         }
39317     }
39318 });
39319 </code></pre>
39320  *
39321  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
39322  * not already familiar with them.</p>
39323  *
39324  * <p>We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s
39325  * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
39326  * Store, so it is as if we passed this instead:
39327  *
39328 <pre><code>
39329 reader: {
39330     type : 'json',
39331     model: 'User'
39332 }
39333 </code></pre>
39334  *
39335  * <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>
39336  *
39337 <pre><code>
39338 [
39339     {
39340         "id": 1,
39341         "name": "Ed Spencer",
39342         "email": "ed@sencha.com"
39343     },
39344     {
39345         "id": 2,
39346         "name": "Abe Elias",
39347         "email": "abe@sencha.com"
39348     }
39349 ]
39350 </code></pre>
39351  *
39352  * <p><u>Reading other JSON formats</u></p>
39353  *
39354  * <p>If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
39355  * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the
39356  * {@link #root} configuration to parse data that comes back like this:</p>
39357  *
39358 <pre><code>
39359 {
39360     "users": [
39361        {
39362            "id": 1,
39363            "name": "Ed Spencer",
39364            "email": "ed@sencha.com"
39365        },
39366        {
39367            "id": 2,
39368            "name": "Abe Elias",
39369            "email": "abe@sencha.com"
39370        }
39371     ]
39372 }
39373 </code></pre>
39374  *
39375  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
39376  *
39377 <pre><code>
39378 reader: {
39379     type: 'json',
39380     root: 'users'
39381 }
39382 </code></pre>
39383  *
39384  * <p>Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
39385  * around each record inside a nested structure like this:</p>
39386  *
39387 <pre><code>
39388 {
39389     "total": 122,
39390     "offset": 0,
39391     "users": [
39392         {
39393             "id": "ed-spencer-1",
39394             "value": 1,
39395             "user": {
39396                 "id": 1,
39397                 "name": "Ed Spencer",
39398                 "email": "ed@sencha.com"
39399             }
39400         }
39401     ]
39402 }
39403 </code></pre>
39404  *
39405  * <p>In the case above the record data is nested an additional level inside the "users" array as each "user" item has
39406  * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the
39407  * JSON above we need to specify the {@link #record} configuration like this:</p>
39408  *
39409 <pre><code>
39410 reader: {
39411     type  : 'json',
39412     root  : 'users',
39413     record: 'user'
39414 }
39415 </code></pre>
39416  *
39417  * <p><u>Response metadata</u></p>
39418  *
39419  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
39420  * and the {@link #successProperty success status of the response}. These are typically included in the JSON response
39421  * like this:</p>
39422  *
39423 <pre><code>
39424 {
39425     "total": 100,
39426     "success": true,
39427     "users": [
39428         {
39429             "id": 1,
39430             "name": "Ed Spencer",
39431             "email": "ed@sencha.com"
39432         }
39433     ]
39434 }
39435 </code></pre>
39436  *
39437  * <p>If these properties are present in the JSON response they can be parsed out by the JsonReader and used by the
39438  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
39439  * options:</p>
39440  *
39441 <pre><code>
39442 reader: {
39443     type : 'json',
39444     root : 'users',
39445     totalProperty  : 'total',
39446     successProperty: 'success'
39447 }
39448 </code></pre>
39449  *
39450  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
39451  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
39452  * returned.</p>
39453  */
39454 Ext.define('Ext.data.reader.Json', {
39455     extend: 'Ext.data.reader.Reader',
39456     alternateClassName: 'Ext.data.JsonReader',
39457     alias : 'reader.json',
39458
39459     root: '',
39460
39461     /**
39462      * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
39463      * See the JsonReader intro docs for more details. This is not often needed.
39464      */
39465
39466     /**
39467      * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
39468      * reading values. Defalts to <tt>false</tt>.
39469      * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
39470      * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name
39471      * "foo.bar.baz" direct from the root object.
39472      */
39473     useSimpleAccessors: false,
39474
39475     /**
39476      * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
39477      * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
39478      * @param {Object} data The raw JSON data
39479      * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
39480      */
39481     readRecords: function(data) {
39482         //this has to be before the call to super because we use the meta data in the superclass readRecords
39483         if (data.metaData) {
39484             this.onMetaChange(data.metaData);
39485         }
39486
39487         /**
39488          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
39489          * @property {Object} jsonData
39490          */
39491         this.jsonData = data;
39492         return this.callParent([data]);
39493     },
39494
39495     //inherit docs
39496     getResponseData: function(response) {
39497         var data;
39498         try {
39499             data = Ext.decode(response.responseText);
39500         }
39501         catch (ex) {
39502             Ext.Error.raise({
39503                 response: response,
39504                 json: response.responseText,
39505                 parseError: ex,
39506                 msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
39507             });
39508         }
39509         if (!data) {
39510             Ext.Error.raise('JSON object not found');
39511         }
39512
39513         return data;
39514     },
39515
39516     //inherit docs
39517     buildExtractors : function() {
39518         var me = this;
39519
39520         me.callParent(arguments);
39521
39522         if (me.root) {
39523             me.getRoot = me.createAccessor(me.root);
39524         } else {
39525             me.getRoot = function(root) {
39526                 return root;
39527             };
39528         }
39529     },
39530
39531     /**
39532      * @private
39533      * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
39534      * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
39535      * @param {Object} root The JSON root node
39536      * @return {Ext.data.Model[]} The records
39537      */
39538     extractData: function(root) {
39539         var recordName = this.record,
39540             data = [],
39541             length, i;
39542
39543         if (recordName) {
39544             length = root.length;
39545             
39546             if (!length && Ext.isObject(root)) {
39547                 length = 1;
39548                 root = [root];
39549             }
39550
39551             for (i = 0; i < length; i++) {
39552                 data[i] = root[i][recordName];
39553             }
39554         } else {
39555             data = root;
39556         }
39557         return this.callParent([data]);
39558     },
39559
39560     /**
39561      * @private
39562      * Returns an accessor function for the given property string. Gives support for properties such as the following:
39563      * 'someProperty'
39564      * 'some.property'
39565      * 'some["property"]'
39566      * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
39567      */
39568     createAccessor: function() {
39569         var re = /[\[\.]/;
39570
39571         return function(expr) {
39572             if (Ext.isEmpty(expr)) {
39573                 return Ext.emptyFn;
39574             }
39575             if (Ext.isFunction(expr)) {
39576                 return expr;
39577             }
39578             if (this.useSimpleAccessors !== true) {
39579                 var i = String(expr).search(re);
39580                 if (i >= 0) {
39581                     return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
39582                 }
39583             }
39584             return function(obj) {
39585                 return obj[expr];
39586             };
39587         };
39588     }()
39589 });
39590 /**
39591  * @class Ext.data.writer.Json
39592  * @extends Ext.data.writer.Writer
39593
39594 This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
39595 The {@link #allowSingle} configuration can be set to false to force the records to always be
39596 encoded in an array, even if there is only a single record being sent.
39597
39598  * @markdown
39599  */
39600 Ext.define('Ext.data.writer.Json', {
39601     extend: 'Ext.data.writer.Writer',
39602     alternateClassName: 'Ext.data.JsonWriter',
39603     alias: 'writer.json',
39604     
39605     /**
39606      * @cfg {String} root The key under which the records in this Writer will be placed. Defaults to <tt>undefined</tt>.
39607      * Example generated request, using root: 'records':
39608 <pre><code>
39609 {'records': [{name: 'my record'}, {name: 'another record'}]}
39610 </code></pre>
39611      */
39612     root: undefined,
39613     
39614     /**
39615      * @cfg {Boolean} encode True to use Ext.encode() on the data before sending. Defaults to <tt>false</tt>.
39616      * The encode option should only be set to true when a {@link #root} is defined, because the values will be
39617      * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter
39618      * sent to the server.
39619      */
39620     encode: false,
39621     
39622     /**
39623      * @cfg {Boolean} allowSingle False to ensure that records are always wrapped in an array, even if there is only
39624      * one record being sent. When there is more than one record, they will always be encoded into an array.
39625      * Defaults to <tt>true</tt>. Example:
39626      * <pre><code>
39627 // with allowSingle: true
39628 "root": {
39629     "first": "Mark",
39630     "last": "Corrigan"
39631 }
39632
39633 // with allowSingle: false
39634 "root": [{
39635     "first": "Mark",
39636     "last": "Corrigan"
39637 }]
39638      * </code></pre>
39639      */
39640     allowSingle: true,
39641     
39642     //inherit docs
39643     writeRecords: function(request, data) {
39644         var root = this.root;
39645         
39646         if (this.allowSingle && data.length == 1) {
39647             // convert to single object format
39648             data = data[0];
39649         }
39650         
39651         if (this.encode) {
39652             if (root) {
39653                 // sending as a param, need to encode
39654                 request.params[root] = Ext.encode(data);
39655             } else {
39656                 Ext.Error.raise('Must specify a root when using encode');
39657             }
39658         } else {
39659             // send as jsonData
39660             request.jsonData = request.jsonData || {};
39661             if (root) {
39662                 request.jsonData[root] = data;
39663             } else {
39664                 request.jsonData = data;
39665             }
39666         }
39667         return request;
39668     }
39669 });
39670
39671 /**
39672  * @author Ed Spencer
39673  *
39674  * Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model}
39675  * data. Usually developers will not need to create or interact with proxies directly.
39676  *
39677  * # Types of Proxy
39678  *
39679  * There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}.
39680  * The Client proxies save their data locally and include the following subclasses:
39681  *
39682  * - {@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it
39683  * - {@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage if the browsers supports it
39684  * - {@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed
39685  *
39686  * The Server proxies save their data by sending requests to some remote server. These proxies include:
39687  *
39688  * - {@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain
39689  * - {@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain
39690  * - {@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct.Manager} to send requests
39691  *
39692  * Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four
39693  * operations are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy}
39694  * respectively. Each Proxy subclass implements these functions.
39695  *
39696  * The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation
39697  * encapsulates information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances
39698  * that are to be modified, etc. See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD
39699  * method also accepts a callback function to be called asynchronously on completion.
39700  *
39701  * Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch}
39702  * method.
39703  */
39704 Ext.define('Ext.data.proxy.Proxy', {
39705     alias: 'proxy.proxy',
39706     alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
39707     requires: [
39708         'Ext.data.reader.Json',
39709         'Ext.data.writer.Json'
39710     ],
39711     uses: [
39712         'Ext.data.Batch', 
39713         'Ext.data.Operation', 
39714         'Ext.data.Model'
39715     ],
39716     mixins: {
39717         observable: 'Ext.util.Observable'
39718     },
39719     
39720     /**
39721      * @cfg {String} batchOrder
39722      * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this to set a different
39723      * order for the batched CRUD actions to be executed in. Defaults to 'create,update,destroy'.
39724      */
39725     batchOrder: 'create,update,destroy',
39726     
39727     /**
39728      * @cfg {Boolean} batchActions
39729      * True to batch actions of a particular type when synchronizing the store. Defaults to true.
39730      */
39731     batchActions: true,
39732     
39733     /**
39734      * @cfg {String} defaultReaderType
39735      * The default registered reader type. Defaults to 'json'.
39736      * @private
39737      */
39738     defaultReaderType: 'json',
39739     
39740     /**
39741      * @cfg {String} defaultWriterType
39742      * The default registered writer type. Defaults to 'json'.
39743      * @private
39744      */
39745     defaultWriterType: 'json',
39746     
39747     /**
39748      * @cfg {String/Ext.data.Model} model
39749      * The name of the Model to tie to this Proxy. Can be either the string name of the Model, or a reference to the
39750      * Model constructor. Required.
39751      */
39752     
39753     /**
39754      * @cfg {Object/String/Ext.data.reader.Reader} reader
39755      * The Ext.data.reader.Reader to use to decode the server's response or data read from client. This can either be a
39756      * Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
39757      */
39758     
39759     /**
39760      * @cfg {Object/String/Ext.data.writer.Writer} writer
39761      * The Ext.data.writer.Writer to use to encode any request sent to the server or saved to client. This can either be
39762      * a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
39763      */
39764     
39765     isProxy: true,
39766     
39767     /**
39768      * Creates the Proxy
39769      * @param {Object} config (optional) Config object.
39770      */
39771     constructor: function(config) {
39772         config = config || {};
39773         
39774         if (config.model === undefined) {
39775             delete config.model;
39776         }
39777
39778         this.mixins.observable.constructor.call(this, config);
39779         
39780         if (this.model !== undefined && !(this.model instanceof Ext.data.Model)) {
39781             this.setModel(this.model);
39782         }
39783     },
39784     
39785     /**
39786      * Sets the model associated with this proxy. This will only usually be called by a Store
39787      *
39788      * @param {String/Ext.data.Model} model The new model. Can be either the model name string,
39789      * or a reference to the model's constructor
39790      * @param {Boolean} setOnStore Sets the new model on the associated Store, if one is present
39791      */
39792     setModel: function(model, setOnStore) {
39793         this.model = Ext.ModelManager.getModel(model);
39794         
39795         var reader = this.reader,
39796             writer = this.writer;
39797         
39798         this.setReader(reader);
39799         this.setWriter(writer);
39800         
39801         if (setOnStore && this.store) {
39802             this.store.setModel(this.model);
39803         }
39804     },
39805     
39806     /**
39807      * Returns the model attached to this Proxy
39808      * @return {Ext.data.Model} The model
39809      */
39810     getModel: function() {
39811         return this.model;
39812     },
39813     
39814     /**
39815      * Sets the Proxy's Reader by string, config object or Reader instance
39816      *
39817      * @param {String/Object/Ext.data.reader.Reader} reader The new Reader, which can be either a type string,
39818      * a configuration object or an Ext.data.reader.Reader instance
39819      * @return {Ext.data.reader.Reader} The attached Reader object
39820      */
39821     setReader: function(reader) {
39822         var me = this;
39823         
39824         if (reader === undefined || typeof reader == 'string') {
39825             reader = {
39826                 type: reader
39827             };
39828         }
39829
39830         if (reader.isReader) {
39831             reader.setModel(me.model);
39832         } else {
39833             Ext.applyIf(reader, {
39834                 proxy: me,
39835                 model: me.model,
39836                 type : me.defaultReaderType
39837             });
39838
39839             reader = Ext.createByAlias('reader.' + reader.type, reader);
39840         }
39841         
39842         me.reader = reader;
39843         return me.reader;
39844     },
39845     
39846     /**
39847      * Returns the reader currently attached to this proxy instance
39848      * @return {Ext.data.reader.Reader} The Reader instance
39849      */
39850     getReader: function() {
39851         return this.reader;
39852     },
39853     
39854     /**
39855      * Sets the Proxy's Writer by string, config object or Writer instance
39856      *
39857      * @param {String/Object/Ext.data.writer.Writer} writer The new Writer, which can be either a type string,
39858      * a configuration object or an Ext.data.writer.Writer instance
39859      * @return {Ext.data.writer.Writer} The attached Writer object
39860      */
39861     setWriter: function(writer) {
39862         if (writer === undefined || typeof writer == 'string') {
39863             writer = {
39864                 type: writer
39865             };
39866         }
39867
39868         if (!(writer instanceof Ext.data.writer.Writer)) {
39869             Ext.applyIf(writer, {
39870                 model: this.model,
39871                 type : this.defaultWriterType
39872             });
39873
39874             writer = Ext.createByAlias('writer.' + writer.type, writer);
39875         }
39876         
39877         this.writer = writer;
39878         
39879         return this.writer;
39880     },
39881     
39882     /**
39883      * Returns the writer currently attached to this proxy instance
39884      * @return {Ext.data.writer.Writer} The Writer instance
39885      */
39886     getWriter: function() {
39887         return this.writer;
39888     },
39889     
39890     /**
39891      * Performs the given create operation.
39892      * @param {Ext.data.Operation} operation The Operation to perform
39893      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39894      * @param {Object} scope Scope to execute the callback function in
39895      * @method
39896      */
39897     create: Ext.emptyFn,
39898     
39899     /**
39900      * Performs the given read operation.
39901      * @param {Ext.data.Operation} operation The Operation to perform
39902      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39903      * @param {Object} scope Scope to execute the callback function in
39904      * @method
39905      */
39906     read: Ext.emptyFn,
39907     
39908     /**
39909      * Performs the given update operation.
39910      * @param {Ext.data.Operation} operation The Operation to perform
39911      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39912      * @param {Object} scope Scope to execute the callback function in
39913      * @method
39914      */
39915     update: Ext.emptyFn,
39916     
39917     /**
39918      * Performs the given destroy operation.
39919      * @param {Ext.data.Operation} operation The Operation to perform
39920      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39921      * @param {Object} scope Scope to execute the callback function in
39922      * @method
39923      */
39924     destroy: Ext.emptyFn,
39925     
39926     /**
39927      * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used
39928      * internally by {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
39929      *
39930      *     myProxy.batch({
39931      *         create : [myModel1, myModel2],
39932      *         update : [myModel3],
39933      *         destroy: [myModel4, myModel5]
39934      *     });
39935      *
39936      * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and
39937      * have not been saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been
39938      * saved but should now be destroyed.
39939      *
39940      * @param {Object} operations Object containing the Model instances to act upon, keyed by action name
39941      * @param {Object} listeners (optional) listeners object passed straight through to the Batch -
39942      * see {@link Ext.data.Batch}
39943      * @return {Ext.data.Batch} The newly created Ext.data.Batch object
39944      */
39945     batch: function(operations, listeners) {
39946         var me = this,
39947             batch = Ext.create('Ext.data.Batch', {
39948                 proxy: me,
39949                 listeners: listeners || {}
39950             }),
39951             useBatch = me.batchActions, 
39952             records;
39953         
39954         Ext.each(me.batchOrder.split(','), function(action) {
39955             records = operations[action];
39956             if (records) {
39957                 if (useBatch) {
39958                     batch.add(Ext.create('Ext.data.Operation', {
39959                         action: action,
39960                         records: records
39961                     }));
39962                 } else {
39963                     Ext.each(records, function(record){
39964                         batch.add(Ext.create('Ext.data.Operation', {
39965                             action : action, 
39966                             records: [record]
39967                         }));
39968                     });
39969                 }
39970             }
39971         }, me);
39972         
39973         batch.start();
39974         return batch;
39975     }
39976 }, function() {
39977     // Ext.data.proxy.ProxyMgr.registerType('proxy', this);
39978     
39979     //backwards compatibility
39980     Ext.data.DataProxy = this;
39981     // Ext.deprecate('platform', '2.0', function() {
39982     //     Ext.data.DataProxy = this;
39983     // }, this);
39984 });
39985
39986 /**
39987  * @author Ed Spencer
39988  *
39989  * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
39990  * would not usually be used directly.
39991  *
39992  * ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been
39993  * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now
39994  * an alias of AjaxProxy).
39995  * @private
39996  */
39997 Ext.define('Ext.data.proxy.Server', {
39998     extend: 'Ext.data.proxy.Proxy',
39999     alias : 'proxy.server',
40000     alternateClassName: 'Ext.data.ServerProxy',
40001     uses  : ['Ext.data.Request'],
40002
40003     /**
40004      * @cfg {String} url
40005      * The URL from which to request the data object.
40006      */
40007
40008     /**
40009      * @cfg {String} pageParam
40010      * The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to undefined if you don't
40011      * want to send a page parameter.
40012      */
40013     pageParam: 'page',
40014
40015     /**
40016      * @cfg {String} startParam
40017      * The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this to undefined if you don't
40018      * want to send a start parameter.
40019      */
40020     startParam: 'start',
40021
40022     /**
40023      * @cfg {String} limitParam
40024      * The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this to undefined if you don't
40025      * want to send a limit parameter.
40026      */
40027     limitParam: 'limit',
40028
40029     /**
40030      * @cfg {String} groupParam
40031      * The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this to undefined if you don't
40032      * want to send a group parameter.
40033      */
40034     groupParam: 'group',
40035
40036     /**
40037      * @cfg {String} sortParam
40038      * The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this to undefined if you don't
40039      * want to send a sort parameter.
40040      */
40041     sortParam: 'sort',
40042
40043     /**
40044      * @cfg {String} filterParam
40045      * The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set this to undefined if you don't
40046      * want to send a filter parameter.
40047      */
40048     filterParam: 'filter',
40049
40050     /**
40051      * @cfg {String} directionParam
40052      * The name of the direction parameter to send in a request. **This is only used when simpleSortMode is set to
40053      * true.** Defaults to 'dir'.
40054      */
40055     directionParam: 'dir',
40056
40057     /**
40058      * @cfg {Boolean} simpleSortMode
40059      * Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a
40060      * remote sort is requested. The directionParam and sortParam will be sent with the property name and either 'ASC'
40061      * or 'DESC'.
40062      */
40063     simpleSortMode: false,
40064
40065     /**
40066      * @cfg {Boolean} noCache
40067      * Disable caching by adding a unique parameter name to the request. Set to false to allow caching. Defaults to true.
40068      */
40069     noCache : true,
40070
40071     /**
40072      * @cfg {String} cacheString
40073      * The name of the cache param added to the url when using noCache. Defaults to "_dc".
40074      */
40075     cacheString: "_dc",
40076
40077     /**
40078      * @cfg {Number} timeout
40079      * The number of milliseconds to wait for a response. Defaults to 30000 milliseconds (30 seconds).
40080      */
40081     timeout : 30000,
40082
40083     /**
40084      * @cfg {Object} api
40085      * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
40086      *
40087      *     api: {
40088      *         create  : undefined,
40089      *         read    : undefined,
40090      *         update  : undefined,
40091      *         destroy : undefined
40092      *     }
40093      *
40094      * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
40095      * {@link #api} property, or if undefined default to the configured
40096      * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
40097      *
40098      * For example:
40099      *
40100      *     api: {
40101      *         create  : '/controller/new',
40102      *         read    : '/controller/load',
40103      *         update  : '/controller/update',
40104      *         destroy : '/controller/destroy_action'
40105      *     }
40106      *
40107      * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
40108      * configured {@link Ext.data.proxy.Server#url url}.
40109      */
40110
40111     constructor: function(config) {
40112         var me = this;
40113
40114         config = config || {};
40115         this.addEvents(
40116             /**
40117              * @event exception
40118              * Fires when the server returns an exception
40119              * @param {Ext.data.proxy.Proxy} this
40120              * @param {Object} response The response from the AJAX request
40121              * @param {Ext.data.Operation} operation The operation that triggered request
40122              */
40123             'exception'
40124         );
40125         me.callParent([config]);
40126
40127         /**
40128          * @cfg {Object} extraParams
40129          * Extra parameters that will be included on every request. Individual requests with params of the same name
40130          * will override these params when they are in conflict.
40131          */
40132         me.extraParams = config.extraParams || {};
40133
40134         me.api = config.api || {};
40135
40136         //backwards compatibility, will be deprecated in 5.0
40137         me.nocache = me.noCache;
40138     },
40139
40140     //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
40141     create: function() {
40142         return this.doRequest.apply(this, arguments);
40143     },
40144
40145     read: function() {
40146         return this.doRequest.apply(this, arguments);
40147     },
40148
40149     update: function() {
40150         return this.doRequest.apply(this, arguments);
40151     },
40152
40153     destroy: function() {
40154         return this.doRequest.apply(this, arguments);
40155     },
40156
40157     /**
40158      * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
40159      * that this Proxy is attached to.
40160      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
40161      * @return {Ext.data.Request} The request object
40162      */
40163     buildRequest: function(operation) {
40164         var params = Ext.applyIf(operation.params || {}, this.extraParams || {}),
40165             request;
40166
40167         //copy any sorters, filters etc into the params so they can be sent over the wire
40168         params = Ext.applyIf(params, this.getParams(operation));
40169
40170         if (operation.id && !params.id) {
40171             params.id = operation.id;
40172         }
40173
40174         request = Ext.create('Ext.data.Request', {
40175             params   : params,
40176             action   : operation.action,
40177             records  : operation.records,
40178             operation: operation,
40179             url      : operation.url
40180         });
40181
40182         request.url = this.buildUrl(request);
40183
40184         /*
40185          * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
40186          * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
40187          */
40188         operation.request = request;
40189
40190         return request;
40191     },
40192
40193     // Should this be documented as protected method?
40194     processResponse: function(success, operation, request, response, callback, scope){
40195         var me = this,
40196             reader,
40197             result;
40198
40199         if (success === true) {
40200             reader = me.getReader();
40201             result = reader.read(me.extractResponseData(response));
40202
40203             if (result.success !== false) {
40204                 //see comment in buildRequest for why we include the response object here
40205                 Ext.apply(operation, {
40206                     response: response,
40207                     resultSet: result
40208                 });
40209
40210                 operation.commitRecords(result.records);
40211                 operation.setCompleted();
40212                 operation.setSuccessful();
40213             } else {
40214                 operation.setException(result.message);
40215                 me.fireEvent('exception', this, response, operation);
40216             }
40217         } else {
40218             me.setException(operation, response);
40219             me.fireEvent('exception', this, response, operation);
40220         }
40221
40222         //this callback is the one that was passed to the 'read' or 'write' function above
40223         if (typeof callback == 'function') {
40224             callback.call(scope || me, operation);
40225         }
40226
40227         me.afterRequest(request, success);
40228     },
40229
40230     /**
40231      * Sets up an exception on the operation
40232      * @private
40233      * @param {Ext.data.Operation} operation The operation
40234      * @param {Object} response The response
40235      */
40236     setException: function(operation, response){
40237         operation.setException({
40238             status: response.status,
40239             statusText: response.statusText
40240         });
40241     },
40242
40243     /**
40244      * Template method to allow subclasses to specify how to get the response for the reader.
40245      * @template
40246      * @private
40247      * @param {Object} response The server response
40248      * @return {Object} The response data to be used by the reader
40249      */
40250     extractResponseData: function(response){
40251         return response;
40252     },
40253
40254     /**
40255      * Encode any values being sent to the server. Can be overridden in subclasses.
40256      * @private
40257      * @param {Array} An array of sorters/filters.
40258      * @return {Object} The encoded value
40259      */
40260     applyEncoding: function(value){
40261         return Ext.encode(value);
40262     },
40263
40264     /**
40265      * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
40266      * this simply JSON-encodes the sorter data
40267      * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
40268      * @return {String} The encoded sorters
40269      */
40270     encodeSorters: function(sorters) {
40271         var min = [],
40272             length = sorters.length,
40273             i = 0;
40274
40275         for (; i < length; i++) {
40276             min[i] = {
40277                 property : sorters[i].property,
40278                 direction: sorters[i].direction
40279             };
40280         }
40281         return this.applyEncoding(min);
40282
40283     },
40284
40285     /**
40286      * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
40287      * this simply JSON-encodes the filter data
40288      * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
40289      * @return {String} The encoded filters
40290      */
40291     encodeFilters: function(filters) {
40292         var min = [],
40293             length = filters.length,
40294             i = 0;
40295
40296         for (; i < length; i++) {
40297             min[i] = {
40298                 property: filters[i].property,
40299                 value   : filters[i].value
40300             };
40301         }
40302         return this.applyEncoding(min);
40303     },
40304
40305     /**
40306      * @private
40307      * Copy any sorters, filters etc into the params so they can be sent over the wire
40308      */
40309     getParams: function(operation) {
40310         var me             = this,
40311             params         = {},
40312             isDef          = Ext.isDefined,
40313             groupers       = operation.groupers,
40314             sorters        = operation.sorters,
40315             filters        = operation.filters,
40316             page           = operation.page,
40317             start          = operation.start,
40318             limit          = operation.limit,
40319
40320             simpleSortMode = me.simpleSortMode,
40321
40322             pageParam      = me.pageParam,
40323             startParam     = me.startParam,
40324             limitParam     = me.limitParam,
40325             groupParam     = me.groupParam,
40326             sortParam      = me.sortParam,
40327             filterParam    = me.filterParam,
40328             directionParam = me.directionParam;
40329
40330         if (pageParam && isDef(page)) {
40331             params[pageParam] = page;
40332         }
40333
40334         if (startParam && isDef(start)) {
40335             params[startParam] = start;
40336         }
40337
40338         if (limitParam && isDef(limit)) {
40339             params[limitParam] = limit;
40340         }
40341
40342         if (groupParam && groupers && groupers.length > 0) {
40343             // Grouper is a subclass of sorter, so we can just use the sorter method
40344             params[groupParam] = me.encodeSorters(groupers);
40345         }
40346
40347         if (sortParam && sorters && sorters.length > 0) {
40348             if (simpleSortMode) {
40349                 params[sortParam] = sorters[0].property;
40350                 params[directionParam] = sorters[0].direction;
40351             } else {
40352                 params[sortParam] = me.encodeSorters(sorters);
40353             }
40354
40355         }
40356
40357         if (filterParam && filters && filters.length > 0) {
40358             params[filterParam] = me.encodeFilters(filters);
40359         }
40360
40361         return params;
40362     },
40363
40364     /**
40365      * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
40366      * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
40367      * @param {Ext.data.Request} request The request object
40368      * @return {String} The url
40369      */
40370     buildUrl: function(request) {
40371         var me = this,
40372             url = me.getUrl(request);
40373
40374         if (!url) {
40375             Ext.Error.raise("You are using a ServerProxy but have not supplied it with a url.");
40376         }
40377
40378         if (me.noCache) {
40379             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
40380         }
40381
40382         return url;
40383     },
40384
40385     /**
40386      * Get the url for the request taking into account the order of priority,
40387      * - The request
40388      * - The api
40389      * - The url
40390      * @private
40391      * @param {Ext.data.Request} request The request
40392      * @return {String} The url
40393      */
40394     getUrl: function(request){
40395         return request.url || this.api[request.action] || this.url;
40396     },
40397
40398     /**
40399      * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
40400      * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
40401      * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
40402      * each of the methods that delegate to it.
40403      *
40404      * @param {Ext.data.Operation} operation The Ext.data.Operation object
40405      * @param {Function} callback The callback function to call when the Operation has completed
40406      * @param {Object} scope The scope in which to execute the callback
40407      */
40408     doRequest: function(operation, callback, scope) {
40409         Ext.Error.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
40410     },
40411
40412     /**
40413      * Optional callback function which can be used to clean up after a request has been completed.
40414      * @param {Ext.data.Request} request The Request object
40415      * @param {Boolean} success True if the request was successful
40416      * @method
40417      */
40418     afterRequest: Ext.emptyFn,
40419
40420     onDestroy: function() {
40421         Ext.destroy(this.reader, this.writer);
40422     }
40423 });
40424
40425 /**
40426  * @author Ed Spencer
40427  *
40428  * AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to load
40429  * data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical setup.
40430  * 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
40431  * Model}:
40432  *
40433  *     Ext.define('User', {
40434  *         extend: 'Ext.data.Model',
40435  *         fields: ['id', 'name', 'email']
40436  *     });
40437  *
40438  *     //The Store contains the AjaxProxy as an inline configuration
40439  *     var store = Ext.create('Ext.data.Store', {
40440  *         model: 'User',
40441  *         proxy: {
40442  *             type: 'ajax',
40443  *             url : 'users.json'
40444  *         }
40445  *     });
40446  *
40447  *     store.load();
40448  *
40449  * Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model} with
40450  * the fields that we expect the server to return. Next we set up the Store itself, along with a
40451  * {@link Ext.data.Store#proxy proxy} configuration. This configuration was automatically turned into an
40452  * Ext.data.proxy.Ajax instance, with the url we specified being passed into AjaxProxy's constructor.
40453  * It's as if we'd done this:
40454  *
40455  *     new Ext.data.proxy.Ajax({
40456  *         url: 'users.json',
40457  *         model: 'User',
40458  *         reader: 'json'
40459  *     });
40460  *
40461  * A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default when we
40462  * create the proxy via the Store - the Store already knows about the Model, and Proxy's default {@link
40463  * Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.
40464  *
40465  * Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
40466  * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see
40467  * {@link #actionMethods} to customize this - by default any kind of read will be sent as a GET request and any kind of write
40468  * will be sent as a POST request).
40469  *
40470  * # Limitations
40471  *
40472  * AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com it
40473  * cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
40474  * talking to each other via AJAX.
40475  *
40476  * If you need to read data from another domain and can't set up a proxy server (some software that runs on your own
40477  * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
40478  * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with
40479  * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
40480  * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.
40481  *
40482  * # Readers and Writers
40483  *
40484  * AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response.
40485  * If no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader
40486  * configuration can be passed in as a simple object, which the Proxy automatically turns into a {@link
40487  * Ext.data.reader.Reader Reader} instance:
40488  *
40489  *     var proxy = new Ext.data.proxy.Ajax({
40490  *         model: 'User',
40491  *         reader: {
40492  *             type: 'xml',
40493  *             root: 'users'
40494  *         }
40495  *     });
40496  *
40497  *     proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
40498  *
40499  * # Url generation
40500  *
40501  * AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
40502  * each request. These are controlled with the following configuration options:
40503  *
40504  * - {@link #pageParam} - controls how the page number is sent to the server (see also {@link #startParam} and {@link #limitParam})
40505  * - {@link #sortParam} - controls how sort information is sent to the server
40506  * - {@link #groupParam} - controls how grouping information is sent to the server
40507  * - {@link #filterParam} - controls how filter information is sent to the server
40508  *
40509  * Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can customize
40510  * the generated urls, let's say we're loading the Proxy with the following Operation:
40511  *
40512  *     var operation = new Ext.data.Operation({
40513  *         action: 'read',
40514  *         page  : 2
40515  *     });
40516  *
40517  * Now we'll issue the request for this Operation by calling {@link #read}:
40518  *
40519  *     var proxy = new Ext.data.proxy.Ajax({
40520  *         url: '/users'
40521  *     });
40522  *
40523  *     proxy.read(operation); //GET /users?page=2
40524  *
40525  * Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is sent
40526  * to the server:
40527  *
40528  *     var proxy = new Ext.data.proxy.Ajax({
40529  *         url: '/users',
40530  *         pagePage: 'pageNumber'
40531  *     });
40532  *
40533  *     proxy.read(operation); //GET /users?pageNumber=2
40534  *
40535  * Alternatively, our Operation could have been configured to send start and limit parameters instead of page:
40536  *
40537  *     var operation = new Ext.data.Operation({
40538  *         action: 'read',
40539  *         start : 50,
40540  *         limit : 25
40541  *     });
40542  *
40543  *     var proxy = new Ext.data.proxy.Ajax({
40544  *         url: '/users'
40545  *     });
40546  *
40547  *     proxy.read(operation); //GET /users?start=50&limit;=25
40548  *
40549  * Again we can customize this url:
40550  *
40551  *     var proxy = new Ext.data.proxy.Ajax({
40552  *         url: '/users',
40553  *         startParam: 'startIndex',
40554  *         limitParam: 'limitIndex'
40555  *     });
40556  *
40557  *     proxy.read(operation); //GET /users?startIndex=50&limitIndex;=25
40558  *
40559  * AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a more
40560  * expressive Operation object:
40561  *
40562  *     var operation = new Ext.data.Operation({
40563  *         action: 'read',
40564  *         sorters: [
40565  *             new Ext.util.Sorter({
40566  *                 property : 'name',
40567  *                 direction: 'ASC'
40568  *             }),
40569  *             new Ext.util.Sorter({
40570  *                 property : 'age',
40571  *                 direction: 'DESC'
40572  *             })
40573  *         ],
40574  *         filters: [
40575  *             new Ext.util.Filter({
40576  *                 property: 'eyeColor',
40577  *                 value   : 'brown'
40578  *             })
40579  *         ]
40580  *     });
40581  *
40582  * This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters and
40583  * filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like this
40584  * (note that the url is escaped before sending the request, but is left unescaped here for clarity):
40585  *
40586  *     var proxy = new Ext.data.proxy.Ajax({
40587  *         url: '/users'
40588  *     });
40589  *
40590  *     proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter;=[{"property":"eyeColor","value":"brown"}]
40591  *
40592  * We can again customize how this is created by supplying a few configuration options. Let's say our server is set up
40593  * to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
40594  * that format like this:
40595  *
40596  *      var proxy = new Ext.data.proxy.Ajax({
40597  *          url: '/users',
40598  *          sortParam: 'sortBy',
40599  *          filterParam: 'filterBy',
40600  *
40601  *          //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
40602  *          encodeSorters: function(sorters) {
40603  *              var length   = sorters.length,
40604  *                  sortStrs = [],
40605  *                  sorter, i;
40606  *
40607  *              for (i = 0; i < length; i++) {
40608  *                  sorter = sorters[i];
40609  *
40610  *                  sortStrs[i] = sorter.property + '#' + sorter.direction
40611  *              }
40612  *
40613  *              return sortStrs.join(",");
40614  *          }
40615  *      });
40616  *
40617  *      proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy;=[{"property":"eyeColor","value":"brown"}]
40618  *
40619  * We can also provide a custom {@link #encodeFilters} function to encode our filters.
40620  *
40621  * @constructor
40622  * Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the Store's call to
40623  * {@link Ext.data.Store#load load} will override any specified callback and params options. In this case, use the
40624  * {@link Ext.data.Store Store}'s events to modify parameters, or react to loading events.
40625  *
40626  * @param {Object} config (optional) Config object.
40627  * If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make the request.
40628  */
40629 Ext.define('Ext.data.proxy.Ajax', {
40630     requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
40631     extend: 'Ext.data.proxy.Server',
40632     alias: 'proxy.ajax',
40633     alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
40634     
40635     /**
40636      * @property {Object} actionMethods
40637      * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions
40638      * and 'POST' for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the
40639      * correct RESTful methods.
40640      */
40641     actionMethods: {
40642         create : 'POST',
40643         read   : 'GET',
40644         update : 'POST',
40645         destroy: 'POST'
40646     },
40647     
40648     /**
40649      * @cfg {Object} headers
40650      * Any headers to add to the Ajax request. Defaults to undefined.
40651      */
40652     
40653     /**
40654      * @ignore
40655      */
40656     doRequest: function(operation, callback, scope) {
40657         var writer  = this.getWriter(),
40658             request = this.buildRequest(operation, callback, scope);
40659             
40660         if (operation.allowWrite()) {
40661             request = writer.write(request);
40662         }
40663         
40664         Ext.apply(request, {
40665             headers       : this.headers,
40666             timeout       : this.timeout,
40667             scope         : this,
40668             callback      : this.createRequestCallback(request, operation, callback, scope),
40669             method        : this.getMethod(request),
40670             disableCaching: false // explicitly set it to false, ServerProxy handles caching
40671         });
40672         
40673         Ext.Ajax.request(request);
40674         
40675         return request;
40676     },
40677     
40678     /**
40679      * Returns the HTTP method name for a given request. By default this returns based on a lookup on
40680      * {@link #actionMethods}.
40681      * @param {Ext.data.Request} request The request object
40682      * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
40683      */
40684     getMethod: function(request) {
40685         return this.actionMethods[request.action];
40686     },
40687     
40688     /**
40689      * @private
40690      * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
40691      * of code duplication inside the returned function so we need to find a way to DRY this up.
40692      * @param {Ext.data.Request} request The Request object
40693      * @param {Ext.data.Operation} operation The Operation being executed
40694      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
40695      * passed to doRequest
40696      * @param {Object} scope The scope in which to execute the callback function
40697      * @return {Function} The callback function
40698      */
40699     createRequestCallback: function(request, operation, callback, scope) {
40700         var me = this;
40701         
40702         return function(options, success, response) {
40703             me.processResponse(success, operation, request, response, callback, scope);
40704         };
40705     }
40706 }, function() {
40707     //backwards compatibility, remove in Ext JS 5.0
40708     Ext.data.HttpProxy = this;
40709 });
40710
40711 /**
40712  * @author Ed Spencer
40713  *
40714  * A Model represents some object that your application manages. For example, one might define a Model for Users,
40715  * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
40716  * {@link Ext.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
40717  * of the data-bound components in Ext.
40718  *
40719  * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
40720  *
40721  *     Ext.define('User', {
40722  *         extend: 'Ext.data.Model',
40723  *         fields: [
40724  *             {name: 'name',  type: 'string'},
40725  *             {name: 'age',   type: 'int'},
40726  *             {name: 'phone', type: 'string'},
40727  *             {name: 'alive', type: 'boolean', defaultValue: true}
40728  *         ],
40729  *
40730  *         changeName: function() {
40731  *             var oldName = this.get('name'),
40732  *                 newName = oldName + " The Barbarian";
40733  *
40734  *             this.set('name', newName);
40735  *         }
40736  *     });
40737  *
40738  * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
40739  * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
40740  *
40741  * Now we can create instances of our User model and call any model logic we defined:
40742  *
40743  *     var user = Ext.create('User', {
40744  *         name : 'Conan',
40745  *         age  : 24,
40746  *         phone: '555-555-5555'
40747  *     });
40748  *
40749  *     user.changeName();
40750  *     user.get('name'); //returns "Conan The Barbarian"
40751  *
40752  * # Validations
40753  *
40754  * Models have built-in support for validations, which are executed against the validator functions in {@link
40755  * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
40756  * models:
40757  *
40758  *     Ext.define('User', {
40759  *         extend: 'Ext.data.Model',
40760  *         fields: [
40761  *             {name: 'name',     type: 'string'},
40762  *             {name: 'age',      type: 'int'},
40763  *             {name: 'phone',    type: 'string'},
40764  *             {name: 'gender',   type: 'string'},
40765  *             {name: 'username', type: 'string'},
40766  *             {name: 'alive',    type: 'boolean', defaultValue: true}
40767  *         ],
40768  *
40769  *         validations: [
40770  *             {type: 'presence',  field: 'age'},
40771  *             {type: 'length',    field: 'name',     min: 2},
40772  *             {type: 'inclusion', field: 'gender',   list: ['Male', 'Female']},
40773  *             {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
40774  *             {type: 'format',    field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
40775  *         ]
40776  *     });
40777  *
40778  * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
40779  * object:
40780  *
40781  *     var instance = Ext.create('User', {
40782  *         name: 'Ed',
40783  *         gender: 'Male',
40784  *         username: 'edspencer'
40785  *     });
40786  *
40787  *     var errors = instance.validate();
40788  *
40789  * # Associations
40790  *
40791  * Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and {@link
40792  * Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
40793  * application which deals with Users, Posts and Comments. We can express the relationships between these models like
40794  * this:
40795  *
40796  *     Ext.define('Post', {
40797  *         extend: 'Ext.data.Model',
40798  *         fields: ['id', 'user_id'],
40799  *
40800  *         belongsTo: 'User',
40801  *         hasMany  : {model: 'Comment', name: 'comments'}
40802  *     });
40803  *
40804  *     Ext.define('Comment', {
40805  *         extend: 'Ext.data.Model',
40806  *         fields: ['id', 'user_id', 'post_id'],
40807  *
40808  *         belongsTo: 'Post'
40809  *     });
40810  *
40811  *     Ext.define('User', {
40812  *         extend: 'Ext.data.Model',
40813  *         fields: ['id'],
40814  *
40815  *         hasMany: [
40816  *             'Post',
40817  *             {model: 'Comment', name: 'comments'}
40818  *         ]
40819  *     });
40820  *
40821  * See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the
40822  * usage and configuration of associations. Note that associations can also be specified like this:
40823  *
40824  *     Ext.define('User', {
40825  *         extend: 'Ext.data.Model',
40826  *         fields: ['id'],
40827  *
40828  *         associations: [
40829  *             {type: 'hasMany', model: 'Post',    name: 'posts'},
40830  *             {type: 'hasMany', model: 'Comment', name: 'comments'}
40831  *         ]
40832  *     });
40833  *
40834  * # Using a Proxy
40835  *
40836  * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
40837  * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
40838  * can be set directly on the Model:
40839  *
40840  *     Ext.define('User', {
40841  *         extend: 'Ext.data.Model',
40842  *         fields: ['id', 'name', 'email'],
40843  *
40844  *         proxy: {
40845  *             type: 'rest',
40846  *             url : '/users'
40847  *         }
40848  *     });
40849  *
40850  * 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
40851  * RESTful backend. Let's see how this works:
40852  *
40853  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
40854  *
40855  *     user.save(); //POST /users
40856  *
40857  * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
40858  * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
40859  * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
40860  * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
40861  *
40862  * Loading data via the Proxy is equally easy:
40863  *
40864  *     //get a reference to the User model class
40865  *     var User = Ext.ModelManager.getModel('User');
40866  *
40867  *     //Uses the configured RestProxy to make a GET request to /users/123
40868  *     User.load(123, {
40869  *         success: function(user) {
40870  *             console.log(user.getId()); //logs 123
40871  *         }
40872  *     });
40873  *
40874  * Models can also be updated and destroyed easily:
40875  *
40876  *     //the user Model we loaded in the last snippet:
40877  *     user.set('name', 'Edward Spencer');
40878  *
40879  *     //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
40880  *     user.save({
40881  *         success: function() {
40882  *             console.log('The User was updated');
40883  *         }
40884  *     });
40885  *
40886  *     //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
40887  *     user.destroy({
40888  *         success: function() {
40889  *             console.log('The User was destroyed!');
40890  *         }
40891  *     });
40892  *
40893  * # Usage in Stores
40894  *
40895  * 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
40896  * creating a {@link Ext.data.Store Store}:
40897  *
40898  *     var store = Ext.create('Ext.data.Store', {
40899  *         model: 'User'
40900  *     });
40901  *
40902  *     //uses the Proxy we set up on Model to load the Store data
40903  *     store.load();
40904  *
40905  * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
40906  * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
40907  * Ext.data.Store Store docs} for more information on Stores.
40908  *
40909  * @constructor
40910  * Creates new Model instance.
40911  * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
40912  * @param {Number} id (optional) Unique ID to assign to this model instance
40913  */
40914 Ext.define('Ext.data.Model', {
40915     alternateClassName: 'Ext.data.Record',
40916
40917     mixins: {
40918         observable: 'Ext.util.Observable'
40919     },
40920
40921     requires: [
40922         'Ext.ModelManager',
40923         'Ext.data.IdGenerator',
40924         'Ext.data.Field',
40925         'Ext.data.Errors',
40926         'Ext.data.Operation',
40927         'Ext.data.validations',
40928         'Ext.data.proxy.Ajax',
40929         'Ext.util.MixedCollection'
40930     ],
40931
40932     onClassExtended: function(cls, data) {
40933         var onBeforeClassCreated = data.onBeforeClassCreated;
40934
40935         data.onBeforeClassCreated = function(cls, data) {
40936             var me = this,
40937                 name = Ext.getClassName(cls),
40938                 prototype = cls.prototype,
40939                 superCls = cls.prototype.superclass,
40940
40941                 validations = data.validations || [],
40942                 fields = data.fields || [],
40943                 associations = data.associations || [],
40944                 belongsTo = data.belongsTo,
40945                 hasMany = data.hasMany,
40946                 idgen = data.idgen,
40947
40948                 fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
40949                     return field.name;
40950                 }),
40951
40952                 associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
40953                     return association.name;
40954                 }),
40955
40956                 superValidations = superCls.validations,
40957                 superFields = superCls.fields,
40958                 superAssociations = superCls.associations,
40959
40960                 association, i, ln,
40961                 dependencies = [];
40962
40963             // Save modelName on class and its prototype
40964             cls.modelName = name;
40965             prototype.modelName = name;
40966
40967             // Merge the validations of the superclass and the new subclass
40968             if (superValidations) {
40969                 validations = superValidations.concat(validations);
40970             }
40971
40972             data.validations = validations;
40973
40974             // Merge the fields of the superclass and the new subclass
40975             if (superFields) {
40976                 fields = superFields.items.concat(fields);
40977             }
40978
40979             for (i = 0, ln = fields.length; i < ln; ++i) {
40980                 fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
40981             }
40982
40983             data.fields = fieldsMixedCollection;
40984
40985             if (idgen) {
40986                 data.idgen = Ext.data.IdGenerator.get(idgen);
40987             }
40988
40989             //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
40990             //we support that here
40991             if (belongsTo) {
40992                 belongsTo = Ext.Array.from(belongsTo);
40993
40994                 for (i = 0, ln = belongsTo.length; i < ln; ++i) {
40995                     association = belongsTo[i];
40996
40997                     if (!Ext.isObject(association)) {
40998                         association = {model: association};
40999                     }
41000
41001                     association.type = 'belongsTo';
41002                     associations.push(association);
41003                 }
41004
41005                 delete data.belongsTo;
41006             }
41007
41008             if (hasMany) {
41009                 hasMany = Ext.Array.from(hasMany);
41010                 for (i = 0, ln = hasMany.length; i < ln; ++i) {
41011                     association = hasMany[i];
41012
41013                     if (!Ext.isObject(association)) {
41014                         association = {model: association};
41015                     }
41016
41017                     association.type = 'hasMany';
41018                     associations.push(association);
41019                 }
41020
41021                 delete data.hasMany;
41022             }
41023
41024             if (superAssociations) {
41025                 associations = superAssociations.items.concat(associations);
41026             }
41027
41028             for (i = 0, ln = associations.length; i < ln; ++i) {
41029                 dependencies.push('association.' + associations[i].type.toLowerCase());
41030             }
41031
41032             if (data.proxy) {
41033                 if (typeof data.proxy === 'string') {
41034                     dependencies.push('proxy.' + data.proxy);
41035                 }
41036                 else if (typeof data.proxy.type === 'string') {
41037                     dependencies.push('proxy.' + data.proxy.type);
41038                 }
41039             }
41040
41041             Ext.require(dependencies, function() {
41042                 Ext.ModelManager.registerType(name, cls);
41043
41044                 for (i = 0, ln = associations.length; i < ln; ++i) {
41045                     association = associations[i];
41046
41047                     Ext.apply(association, {
41048                         ownerModel: name,
41049                         associatedModel: association.model
41050                     });
41051
41052                     if (Ext.ModelManager.getModel(association.model) === undefined) {
41053                         Ext.ModelManager.registerDeferredAssociation(association);
41054                     } else {
41055                         associationsMixedCollection.add(Ext.data.Association.create(association));
41056                     }
41057                 }
41058
41059                 data.associations = associationsMixedCollection;
41060
41061                 onBeforeClassCreated.call(me, cls, data);
41062
41063                 cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
41064
41065                 // Fire the onModelDefined template method on ModelManager
41066                 Ext.ModelManager.onModelDefined(cls);
41067             });
41068         };
41069     },
41070
41071     inheritableStatics: {
41072         /**
41073          * Sets the Proxy to use for this model. Accepts any options that can be accepted by
41074          * {@link Ext#createByAlias Ext.createByAlias}.
41075          * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
41076          * @return {Ext.data.proxy.Proxy}
41077          * @static
41078          * @inheritable
41079          */
41080         setProxy: function(proxy) {
41081             //make sure we have an Ext.data.proxy.Proxy object
41082             if (!proxy.isProxy) {
41083                 if (typeof proxy == "string") {
41084                     proxy = {
41085                         type: proxy
41086                     };
41087                 }
41088                 proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
41089             }
41090             proxy.setModel(this);
41091             this.proxy = this.prototype.proxy = proxy;
41092
41093             return proxy;
41094         },
41095
41096         /**
41097          * Returns the configured Proxy for this Model
41098          * @return {Ext.data.proxy.Proxy} The proxy
41099          * @static
41100          * @inheritable
41101          */
41102         getProxy: function() {
41103             return this.proxy;
41104         },
41105
41106         /**
41107          * Asynchronously loads a model instance by id. Sample usage:
41108          *
41109          *     MyApp.User = Ext.define('User', {
41110          *         extend: 'Ext.data.Model',
41111          *         fields: [
41112          *             {name: 'id', type: 'int'},
41113          *             {name: 'name', type: 'string'}
41114          *         ]
41115          *     });
41116          *
41117          *     MyApp.User.load(10, {
41118          *         scope: this,
41119          *         failure: function(record, operation) {
41120          *             //do something if the load failed
41121          *         },
41122          *         success: function(record, operation) {
41123          *             //do something if the load succeeded
41124          *         },
41125          *         callback: function(record, operation) {
41126          *             //do something whether the load succeeded or failed
41127          *         }
41128          *     });
41129          *
41130          * @param {Number} id The id of the model to load
41131          * @param {Object} config (optional) config object containing success, failure and callback functions, plus
41132          * optional scope
41133          * @static
41134          * @inheritable
41135          */
41136         load: function(id, config) {
41137             config = Ext.apply({}, config);
41138             config = Ext.applyIf(config, {
41139                 action: 'read',
41140                 id    : id
41141             });
41142
41143             var operation  = Ext.create('Ext.data.Operation', config),
41144                 scope      = config.scope || this,
41145                 record     = null,
41146                 callback;
41147
41148             callback = function(operation) {
41149                 if (operation.wasSuccessful()) {
41150                     record = operation.getRecords()[0];
41151                     Ext.callback(config.success, scope, [record, operation]);
41152                 } else {
41153                     Ext.callback(config.failure, scope, [record, operation]);
41154                 }
41155                 Ext.callback(config.callback, scope, [record, operation]);
41156             };
41157
41158             this.proxy.read(operation, callback, this);
41159         }
41160     },
41161
41162     statics: {
41163         PREFIX : 'ext-record',
41164         AUTO_ID: 1,
41165         EDIT   : 'edit',
41166         REJECT : 'reject',
41167         COMMIT : 'commit',
41168
41169         /**
41170          * Generates a sequential id. This method is typically called when a record is {@link Ext#create
41171          * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the
41172          * record. The returned id takes the form: {PREFIX}-{AUTO_ID}.
41173          *
41174          * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record')
41175          * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially)
41176          *
41177          * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
41178          * @return {String} auto-generated string id, `"ext-record-i++"`;
41179          * @static
41180          */
41181         id: function(rec) {
41182             var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
41183             rec.phantom = true;
41184             rec.internalId = id;
41185             return id;
41186         }
41187     },
41188
41189     /**
41190      * @cfg {String/Object} idgen
41191      * The id generator to use for this model. The default id generator does not generate
41192      * values for the {@link #idProperty}.
41193      *
41194      * This can be overridden at the model level to provide a custom generator for a model.
41195      * The simplest form of this would be:
41196      *
41197      *      Ext.define('MyApp.data.MyModel', {
41198      *          extend: 'Ext.data.Model',
41199      *          requires: ['Ext.data.SequentialIdGenerator'],
41200      *          idgen: 'sequential',
41201      *          ...
41202      *      });
41203      *
41204      * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such
41205      * as 1, 2, 3 etc..
41206      *
41207      * Another useful id generator is {@link Ext.data.UuidGenerator}:
41208      *
41209      *      Ext.define('MyApp.data.MyModel', {
41210      *          extend: 'Ext.data.Model',
41211      *          requires: ['Ext.data.UuidGenerator'],
41212      *          idgen: 'uuid',
41213      *          ...
41214      *      });
41215      *
41216      * An id generation can also be further configured:
41217      *
41218      *      Ext.define('MyApp.data.MyModel', {
41219      *          extend: 'Ext.data.Model',
41220      *          idgen: {
41221      *              type: 'sequential',
41222      *              seed: 1000,
41223      *              prefix: 'ID_'
41224      *          }
41225      *      });
41226      *
41227      * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
41228      *
41229      * If multiple models share an id space, a single generator can be shared:
41230      *
41231      *      Ext.define('MyApp.data.MyModelX', {
41232      *          extend: 'Ext.data.Model',
41233      *          idgen: {
41234      *              type: 'sequential',
41235      *              id: 'xy'
41236      *          }
41237      *      });
41238      *
41239      *      Ext.define('MyApp.data.MyModelY', {
41240      *          extend: 'Ext.data.Model',
41241      *          idgen: {
41242      *              type: 'sequential',
41243      *              id: 'xy'
41244      *          }
41245      *      });
41246      *
41247      * For more complex, shared id generators, a custom generator is the best approach.
41248      * See {@link Ext.data.IdGenerator} for details on creating custom id generators.
41249      *
41250      * @markdown
41251      */
41252     idgen: {
41253         isGenerator: true,
41254         type: 'default',
41255
41256         generate: function () {
41257             return null;
41258         },
41259         getRecId: function (rec) {
41260             return rec.modelName + '-' + rec.internalId;
41261         }
41262     },
41263
41264     /**
41265      * @property {Boolean} editing
41266      * Internal flag used to track whether or not the model instance is currently being edited. Read-only.
41267      */
41268     editing : false,
41269
41270     /**
41271      * @property {Boolean} dirty
41272      * True if this Record has been modified. Read-only.
41273      */
41274     dirty : false,
41275
41276     /**
41277      * @cfg {String} persistenceProperty
41278      * The property on this Persistable object that its data is saved to. Defaults to 'data'
41279      * (e.g. all persistable data resides in this.data.)
41280      */
41281     persistenceProperty: 'data',
41282
41283     evented: false,
41284     isModel: true,
41285
41286     /**
41287      * @property {Boolean} phantom
41288      * True when the record does not yet exist in a server-side database (see {@link #setDirty}).
41289      * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
41290      */
41291     phantom : false,
41292
41293     /**
41294      * @cfg {String} idProperty
41295      * The name of the field treated as this Model's unique id. Defaults to 'id'.
41296      */
41297     idProperty: 'id',
41298
41299     /**
41300      * @cfg {String} defaultProxyType
41301      * The string type of the default Model Proxy. Defaults to 'ajax'.
41302      */
41303     defaultProxyType: 'ajax',
41304
41305     // Fields config and property
41306     /**
41307      * @cfg {Object[]/String[]} fields
41308      * The fields for this model.
41309      */
41310     /**
41311      * @property {Ext.util.MixedCollection} fields
41312      * The fields defined on this model.
41313      */
41314
41315     /**
41316      * @cfg {Object[]} validations
41317      * An array of {@link Ext.data.validations validations} for this model.
41318      */
41319
41320     // Associations configs and properties
41321     /**
41322      * @cfg {Object[]} associations
41323      * An array of {@link Ext.data.Association associations} for this model.
41324      */
41325     /**
41326      * @cfg {String/Object/String[]/Object[]} hasMany
41327      * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model.
41328      */
41329     /**
41330      * @cfg {String/Object/String[]/Object[]} belongsTo
41331      * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model.
41332      */
41333     /**
41334      * @property {Ext.util.MixedCollection} associations
41335      * {@link Ext.data.Association Associations} defined on this model.
41336      */
41337
41338     /**
41339      * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
41340      * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
41341      */
41342
41343     // raw not documented intentionally, meant to be used internally.
41344     constructor: function(data, id, raw) {
41345         data = data || {};
41346
41347         var me = this,
41348             fields,
41349             length,
41350             field,
41351             name,
41352             i,
41353             newId,
41354             isArray = Ext.isArray(data),
41355             newData = isArray ? {} : null; // to hold mapped array data if needed
41356
41357         /**
41358          * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
41359          * @property internalId
41360          * @type String
41361          * @private
41362          */
41363         me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
41364
41365         /**
41366          * @property {Object} raw The raw data used to create this model if created via a reader.
41367          */
41368         me.raw = raw;
41369
41370         Ext.applyIf(me, {
41371             data: {}
41372         });
41373
41374         /**
41375          * @property {Object} modified Key: value pairs of all fields whose values have changed
41376          */
41377         me.modified = {};
41378
41379         // Deal with spelling error in previous releases
41380         if (me.persistanceProperty) {
41381             if (Ext.isDefined(Ext.global.console)) {
41382                 Ext.global.console.warn('Ext.data.Model: persistanceProperty has been deprecated. Use persistenceProperty instead.');
41383             }
41384             me.persistenceProperty = me.persistanceProperty;
41385         }
41386         me[me.persistenceProperty] = {};
41387
41388         me.mixins.observable.constructor.call(me);
41389
41390         //add default field values if present
41391         fields = me.fields.items;
41392         length = fields.length;
41393
41394         for (i = 0; i < length; i++) {
41395             field = fields[i];
41396             name  = field.name;
41397
41398             if (isArray){
41399                 // Have to map array data so the values get assigned to the named fields
41400                 // rather than getting set as the field names with undefined values.
41401                 newData[name] = data[i];
41402             }
41403             else if (data[name] === undefined) {
41404                 data[name] = field.defaultValue;
41405             }
41406         }
41407
41408         me.set(newData || data);
41409
41410         if (me.getId()) {
41411             me.phantom = false;
41412         } else if (me.phantom) {
41413             newId = me.idgen.generate();
41414             if (newId !== null) {
41415                 me.setId(newId);
41416             }
41417         }
41418
41419         // clear any dirty/modified since we're initializing
41420         me.dirty = false;
41421         me.modified = {};
41422
41423         if (typeof me.init == 'function') {
41424             me.init();
41425         }
41426
41427         me.id = me.idgen.getRecId(me);
41428     },
41429
41430     /**
41431      * Returns the value of the given field
41432      * @param {String} fieldName The field to fetch the value for
41433      * @return {Object} The value
41434      */
41435     get: function(field) {
41436         return this[this.persistenceProperty][field];
41437     },
41438
41439     /**
41440      * Sets the given field to the given value, marks the instance as dirty
41441      * @param {String/Object} fieldName The field to set, or an object containing key/value pairs
41442      * @param {Object} value The value to set
41443      */
41444     set: function(fieldName, value) {
41445         var me = this,
41446             fields = me.fields,
41447             modified = me.modified,
41448             convertFields = [],
41449             field, key, i, currentValue, notEditing, count, length;
41450
41451         /*
41452          * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
41453          * set those last so that all other possible data is set before the convert function is called
41454          */
41455         if (arguments.length == 1 && Ext.isObject(fieldName)) {
41456             notEditing = !me.editing;
41457             count = 0;
41458             for (key in fieldName) {
41459                 if (fieldName.hasOwnProperty(key)) {
41460
41461                     //here we check for the custom convert function. Note that if a field doesn't have a convert function,
41462                     //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
41463                     field = fields.get(key);
41464                     if (field && field.convert !== field.type.convert) {
41465                         convertFields.push(key);
41466                         continue;
41467                     }
41468
41469                     if (!count && notEditing) {
41470                         me.beginEdit();
41471                     }
41472                     ++count;
41473                     me.set(key, fieldName[key]);
41474                 }
41475             }
41476
41477             length = convertFields.length;
41478             if (length) {
41479                 if (!count && notEditing) {
41480                     me.beginEdit();
41481                 }
41482                 count += length;
41483                 for (i = 0; i < length; i++) {
41484                     field = convertFields[i];
41485                     me.set(field, fieldName[field]);
41486                 }
41487             }
41488
41489             if (notEditing && count) {
41490                 me.endEdit();
41491             }
41492         } else {
41493             if (fields) {
41494                 field = fields.get(fieldName);
41495
41496                 if (field && field.convert) {
41497                     value = field.convert(value, me);
41498                 }
41499             }
41500             currentValue = me.get(fieldName);
41501             me[me.persistenceProperty][fieldName] = value;
41502
41503             if (field && field.persist && !me.isEqual(currentValue, value)) {
41504                 if (me.isModified(fieldName)) {
41505                     if (me.isEqual(modified[fieldName], value)) {
41506                         // the original value in me.modified equals the new value, so the
41507                         // field is no longer modified
41508                         delete modified[fieldName];
41509                         // we might have removed the last modified field, so check to see if
41510                         // there are any modified fields remaining and correct me.dirty:
41511                         me.dirty = false;
41512                         for (key in modified) {
41513                             if (modified.hasOwnProperty(key)){
41514                                 me.dirty = true;
41515                                 break;
41516                             }
41517                         }
41518                     }
41519                 } else {
41520                     me.dirty = true;
41521                     modified[fieldName] = currentValue;
41522                 }
41523             }
41524
41525             if (!me.editing) {
41526                 me.afterEdit();
41527             }
41528         }
41529     },
41530
41531     /**
41532      * Checks if two values are equal, taking into account certain
41533      * special factors, for example dates.
41534      * @private
41535      * @param {Object} a The first value
41536      * @param {Object} b The second value
41537      * @return {Boolean} True if the values are equal
41538      */
41539     isEqual: function(a, b){
41540         if (Ext.isDate(a) && Ext.isDate(b)) {
41541             return a.getTime() === b.getTime();
41542         }
41543         return a === b;
41544     },
41545
41546     /**
41547      * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store.
41548      * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
41549      */
41550     beginEdit : function(){
41551         var me = this;
41552         if (!me.editing) {
41553             me.editing = true;
41554             me.dirtySave = me.dirty;
41555             me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
41556             me.modifiedSave = Ext.apply({}, me.modified);
41557         }
41558     },
41559
41560     /**
41561      * Cancels all changes made in the current edit operation.
41562      */
41563     cancelEdit : function(){
41564         var me = this;
41565         if (me.editing) {
41566             me.editing = false;
41567             // reset the modified state, nothing changed since the edit began
41568             me.modified = me.modifiedSave;
41569             me[me.persistenceProperty] = me.dataSave;
41570             me.dirty = me.dirtySave;
41571             delete me.modifiedSave;
41572             delete me.dataSave;
41573             delete me.dirtySave;
41574         }
41575     },
41576
41577     /**
41578      * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
41579      * fire).
41580      * @param {Boolean} silent True to not notify the store of the change
41581      */
41582     endEdit : function(silent){
41583         var me = this,
41584             didChange;
41585             
41586         if (me.editing) {
41587             me.editing = false;
41588             didChange = me.dirty || me.changedWhileEditing();
41589             delete me.modifiedSave;
41590             delete me.dataSave;
41591             delete me.dirtySave;
41592             if (silent !== true && didChange) {
41593                 me.afterEdit();
41594             }
41595         }
41596     },
41597     
41598     /**
41599      * Checks if the underlying data has changed during an edit. This doesn't necessarily
41600      * mean the record is dirty, however we still need to notify the store since it may need
41601      * to update any views.
41602      * @private
41603      * @return {Boolean} True if the underlying data has changed during an edit.
41604      */
41605     changedWhileEditing: function(){
41606         var me = this,
41607             saved = me.dataSave,
41608             data = me[me.persistenceProperty],
41609             key;
41610             
41611         for (key in data) {
41612             if (data.hasOwnProperty(key)) {
41613                 if (!me.isEqual(data[key], saved[key])) {
41614                     return true;
41615                 }
41616             }
41617         }
41618         return false; 
41619     },
41620
41621     /**
41622      * Gets a hash of only the fields that have been modified since this Model was created or commited.
41623      * @return {Object}
41624      */
41625     getChanges : function(){
41626         var modified = this.modified,
41627             changes  = {},
41628             field;
41629
41630         for (field in modified) {
41631             if (modified.hasOwnProperty(field)){
41632                 changes[field] = this.get(field);
41633             }
41634         }
41635
41636         return changes;
41637     },
41638
41639     /**
41640      * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
41641      * @param {String} fieldName {@link Ext.data.Field#name}
41642      * @return {Boolean}
41643      */
41644     isModified : function(fieldName) {
41645         return this.modified.hasOwnProperty(fieldName);
41646     },
41647
41648     /**
41649      * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records
41650      * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
41651      *
41652      * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
41653      * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
41654      */
41655     setDirty : function() {
41656         var me = this,
41657             name;
41658
41659         me.dirty = true;
41660
41661         me.fields.each(function(field) {
41662             if (field.persist) {
41663                 name = field.name;
41664                 me.modified[name] = me.get(name);
41665             }
41666         }, me);
41667     },
41668
41669     markDirty : function() {
41670         if (Ext.isDefined(Ext.global.console)) {
41671             Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
41672         }
41673         return this.setDirty.apply(this, arguments);
41674     },
41675
41676     /**
41677      * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
41678      * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
41679      * reverted to their original values.
41680      *
41681      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
41682      * operations.
41683      *
41684      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
41685      * Defaults to false.
41686      */
41687     reject : function(silent) {
41688         var me = this,
41689             modified = me.modified,
41690             field;
41691
41692         for (field in modified) {
41693             if (modified.hasOwnProperty(field)) {
41694                 if (typeof modified[field] != "function") {
41695                     me[me.persistenceProperty][field] = modified[field];
41696                 }
41697             }
41698         }
41699
41700         me.dirty = false;
41701         me.editing = false;
41702         me.modified = {};
41703
41704         if (silent !== true) {
41705             me.afterReject();
41706         }
41707     },
41708
41709     /**
41710      * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
41711      * instance since either creation or the last commit operation.
41712      *
41713      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
41714      * operations.
41715      *
41716      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
41717      * Defaults to false.
41718      */
41719     commit : function(silent) {
41720         var me = this;
41721
41722         me.phantom = me.dirty = me.editing = false;
41723         me.modified = {};
41724
41725         if (silent !== true) {
41726             me.afterCommit();
41727         }
41728     },
41729
41730     /**
41731      * Creates a copy (clone) of this Model instance.
41732      *
41733      * @param {String} [id] A new id, defaults to the id of the instance being copied.
41734      * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use:
41735      *
41736      *     var rec = record.copy(); // clone the record
41737      *     Ext.data.Model.id(rec); // automatically generate a unique sequential id
41738      *
41739      * @return {Ext.data.Model}
41740      */
41741     copy : function(newId) {
41742         var me = this;
41743
41744         return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId || me.internalId);
41745     },
41746
41747     /**
41748      * Sets the Proxy to use for this model. Accepts any options that can be accepted by
41749      * {@link Ext#createByAlias Ext.createByAlias}.
41750      *
41751      * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
41752      * @return {Ext.data.proxy.Proxy}
41753      */
41754     setProxy: function(proxy) {
41755         //make sure we have an Ext.data.proxy.Proxy object
41756         if (!proxy.isProxy) {
41757             if (typeof proxy === "string") {
41758                 proxy = {
41759                     type: proxy
41760                 };
41761             }
41762             proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
41763         }
41764         proxy.setModel(this.self);
41765         this.proxy = proxy;
41766
41767         return proxy;
41768     },
41769
41770     /**
41771      * Returns the configured Proxy for this Model.
41772      * @return {Ext.data.proxy.Proxy} The proxy
41773      */
41774     getProxy: function() {
41775         return this.proxy;
41776     },
41777
41778     /**
41779      * Validates the current data against all of its configured {@link #validations}.
41780      * @return {Ext.data.Errors} The errors object
41781      */
41782     validate: function() {
41783         var errors      = Ext.create('Ext.data.Errors'),
41784             validations = this.validations,
41785             validators  = Ext.data.validations,
41786             length, validation, field, valid, type, i;
41787
41788         if (validations) {
41789             length = validations.length;
41790
41791             for (i = 0; i < length; i++) {
41792                 validation = validations[i];
41793                 field = validation.field || validation.name;
41794                 type  = validation.type;
41795                 valid = validators[type](validation, this.get(field));
41796
41797                 if (!valid) {
41798                     errors.add({
41799                         field  : field,
41800                         message: validation.message || validators[type + 'Message']
41801                     });
41802                 }
41803             }
41804         }
41805
41806         return errors;
41807     },
41808
41809     /**
41810      * Checks if the model is valid. See {@link #validate}.
41811      * @return {Boolean} True if the model is valid.
41812      */
41813     isValid: function(){
41814         return this.validate().isValid();
41815     },
41816
41817     /**
41818      * Saves the model instance using the configured proxy.
41819      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
41820      * @return {Ext.data.Model} The Model instance
41821      */
41822     save: function(options) {
41823         options = Ext.apply({}, options);
41824
41825         var me     = this,
41826             action = me.phantom ? 'create' : 'update',
41827             record = null,
41828             scope  = options.scope || me,
41829             operation,
41830             callback;
41831
41832         Ext.apply(options, {
41833             records: [me],
41834             action : action
41835         });
41836
41837         operation = Ext.create('Ext.data.Operation', options);
41838
41839         callback = function(operation) {
41840             if (operation.wasSuccessful()) {
41841                 record = operation.getRecords()[0];
41842                 //we need to make sure we've set the updated data here. Ideally this will be redundant once the
41843                 //ModelCache is in place
41844                 me.set(record.data);
41845                 record.dirty = false;
41846
41847                 Ext.callback(options.success, scope, [record, operation]);
41848             } else {
41849                 Ext.callback(options.failure, scope, [record, operation]);
41850             }
41851
41852             Ext.callback(options.callback, scope, [record, operation]);
41853         };
41854
41855         me.getProxy()[action](operation, callback, me);
41856
41857         return me;
41858     },
41859
41860     /**
41861      * Destroys the model using the configured proxy.
41862      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
41863      * @return {Ext.data.Model} The Model instance
41864      */
41865     destroy: function(options){
41866         options = Ext.apply({}, options);
41867
41868         var me     = this,
41869             record = null,
41870             scope  = options.scope || me,
41871             operation,
41872             callback;
41873
41874         Ext.apply(options, {
41875             records: [me],
41876             action : 'destroy'
41877         });
41878
41879         operation = Ext.create('Ext.data.Operation', options);
41880         callback = function(operation) {
41881             if (operation.wasSuccessful()) {
41882                 Ext.callback(options.success, scope, [record, operation]);
41883             } else {
41884                 Ext.callback(options.failure, scope, [record, operation]);
41885             }
41886             Ext.callback(options.callback, scope, [record, operation]);
41887         };
41888
41889         me.getProxy().destroy(operation, callback, me);
41890         return me;
41891     },
41892
41893     /**
41894      * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
41895      * @return {Number} The id
41896      */
41897     getId: function() {
41898         return this.get(this.idProperty);
41899     },
41900
41901     /**
41902      * Sets the model instance's id field to the given id.
41903      * @param {Number} id The new id
41904      */
41905     setId: function(id) {
41906         this.set(this.idProperty, id);
41907     },
41908
41909     /**
41910      * Tells this model instance that it has been added to a store.
41911      * @param {Ext.data.Store} store The store to which this model has been added.
41912      */
41913     join : function(store) {
41914         /**
41915          * @property {Ext.data.Store} store
41916          * The {@link Ext.data.Store Store} to which this Record belongs.
41917          */
41918         this.store = store;
41919     },
41920
41921     /**
41922      * Tells this model instance that it has been removed from the store.
41923      * @param {Ext.data.Store} store The store from which this model has been removed.
41924      */
41925     unjoin: function(store) {
41926         delete this.store;
41927     },
41928
41929     /**
41930      * @private
41931      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41932      * afterEdit method is called
41933      */
41934     afterEdit : function() {
41935         this.callStore('afterEdit');
41936     },
41937
41938     /**
41939      * @private
41940      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41941      * afterReject method is called
41942      */
41943     afterReject : function() {
41944         this.callStore("afterReject");
41945     },
41946
41947     /**
41948      * @private
41949      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41950      * afterCommit method is called
41951      */
41952     afterCommit: function() {
41953         this.callStore('afterCommit');
41954     },
41955
41956     /**
41957      * @private
41958      * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
41959      * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
41960      * will always be called with the model instance as its single argument.
41961      * @param {String} fn The function to call on the store
41962      */
41963     callStore: function(fn) {
41964         var store = this.store;
41965
41966         if (store !== undefined && typeof store[fn] == "function") {
41967             store[fn](this);
41968         }
41969     },
41970
41971     /**
41972      * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
41973      * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
41974      *
41975      *     {
41976      *         orders: [
41977      *             {
41978      *                 id: 123,
41979      *                 status: 'shipped',
41980      *                 orderItems: [
41981      *                     ...
41982      *                 ]
41983      *             }
41984      *         ]
41985      *     }
41986      *
41987      * @return {Object} The nested data set for the Model's loaded associations
41988      */
41989     getAssociatedData: function(){
41990         return this.prepareAssociatedData(this, [], null);
41991     },
41992
41993     /**
41994      * @private
41995      * This complex-looking method takes a given Model instance and returns an object containing all data from
41996      * all of that Model's *loaded* associations. See (@link #getAssociatedData}
41997      * @param {Ext.data.Model} record The Model instance
41998      * @param {String[]} ids PRIVATE. The set of Model instance internalIds that have already been loaded
41999      * @param {String} associationType (optional) The name of the type of association to limit to.
42000      * @return {Object} The nested data set for the Model's loaded associations
42001      */
42002     prepareAssociatedData: function(record, ids, associationType) {
42003         //we keep track of all of the internalIds of the models that we have loaded so far in here
42004         var associations     = record.associations.items,
42005             associationCount = associations.length,
42006             associationData  = {},
42007             associatedStore, associatedName, associatedRecords, associatedRecord,
42008             associatedRecordCount, association, id, i, j, type, allow;
42009
42010         for (i = 0; i < associationCount; i++) {
42011             association = associations[i];
42012             type = association.type;
42013             allow = true;
42014             if (associationType) {
42015                 allow = type == associationType;
42016             }
42017             if (allow && type == 'hasMany') {
42018
42019                 //this is the hasMany store filled with the associated data
42020                 associatedStore = record[association.storeName];
42021
42022                 //we will use this to contain each associated record's data
42023                 associationData[association.name] = [];
42024
42025                 //if it's loaded, put it into the association data
42026                 if (associatedStore && associatedStore.data.length > 0) {
42027                     associatedRecords = associatedStore.data.items;
42028                     associatedRecordCount = associatedRecords.length;
42029
42030                     //now we're finally iterating over the records in the association. We do this recursively
42031                     for (j = 0; j < associatedRecordCount; j++) {
42032                         associatedRecord = associatedRecords[j];
42033                         // Use the id, since it is prefixed with the model name, guaranteed to be unique
42034                         id = associatedRecord.id;
42035
42036                         //when we load the associations for a specific model instance we add it to the set of loaded ids so that
42037                         //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
42038                         if (Ext.Array.indexOf(ids, id) == -1) {
42039                             ids.push(id);
42040
42041                             associationData[association.name][j] = associatedRecord.data;
42042                             Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
42043                         }
42044                     }
42045                 }
42046             } else if (allow && type == 'belongsTo') {
42047                 associatedRecord = record[association.instanceName];
42048                 if (associatedRecord !== undefined) {
42049                     id = associatedRecord.id;
42050                     if (Ext.Array.indexOf(ids, id) == -1) {
42051                         ids.push(id);
42052                         associationData[association.name] = associatedRecord.data;
42053                         Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
42054                     }
42055                 }
42056             }
42057         }
42058
42059         return associationData;
42060     }
42061 });
42062
42063 /**
42064  * @docauthor Evan Trimboli <evan@sencha.com>
42065  *
42066  * Contains a collection of all stores that are created that have an identifier. An identifier can be assigned by
42067  * setting the {@link Ext.data.AbstractStore#storeId storeId} property. When a store is in the StoreManager, it can be
42068  * referred to via it's identifier:
42069  *
42070  *     Ext.create('Ext.data.Store', {
42071  *         model: 'SomeModel',
42072  *         storeId: 'myStore'
42073  *     });
42074  *
42075  *     var store = Ext.data.StoreManager.lookup('myStore');
42076  *
42077  * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.
42078  *
42079  * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when registering
42080  * it with any Component that consumes data from a store:
42081  *
42082  *     Ext.create('Ext.data.Store', {
42083  *         model: 'SomeModel',
42084  *         storeId: 'myStore'
42085  *     });
42086  *
42087  *     Ext.create('Ext.view.View', {
42088  *         store: 'myStore',
42089  *         // other configuration here
42090  *     });
42091  *
42092  */
42093 Ext.define('Ext.data.StoreManager', {
42094     extend: 'Ext.util.MixedCollection',
42095     alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
42096     singleton: true,
42097     uses: ['Ext.data.ArrayStore'],
42098     
42099     /**
42100      * @cfg {Object} listeners @hide
42101      */
42102
42103     /**
42104      * Registers one or more Stores with the StoreManager. You do not normally need to register stores manually. Any
42105      * store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
42106      * @param {Ext.data.Store...} stores Any number of Store instances
42107      */
42108     register : function() {
42109         for (var i = 0, s; (s = arguments[i]); i++) {
42110             this.add(s);
42111         }
42112     },
42113
42114     /**
42115      * Unregisters one or more Stores with the StoreManager
42116      * @param {String/Object...} stores Any number of Store instances or ID-s
42117      */
42118     unregister : function() {
42119         for (var i = 0, s; (s = arguments[i]); i++) {
42120             this.remove(this.lookup(s));
42121         }
42122     },
42123
42124     /**
42125      * Gets a registered Store by id
42126      * @param {String/Object} store The id of the Store, or a Store instance, or a store configuration
42127      * @return {Ext.data.Store}
42128      */
42129     lookup : function(store) {
42130         // handle the case when we are given an array or an array of arrays.
42131         if (Ext.isArray(store)) {
42132             var fields = ['field1'], 
42133                 expand = !Ext.isArray(store[0]),
42134                 data = store,
42135                 i,
42136                 len;
42137                 
42138             if(expand){
42139                 data = [];
42140                 for (i = 0, len = store.length; i < len; ++i) {
42141                     data.push([store[i]]);
42142                 }
42143             } else {
42144                 for(i = 2, len = store[0].length; i <= len; ++i){
42145                     fields.push('field' + i);
42146                 }
42147             }
42148             return Ext.create('Ext.data.ArrayStore', {
42149                 data  : data,
42150                 fields: fields,
42151                 autoDestroy: true,
42152                 autoCreated: true,
42153                 expanded: expand
42154             });
42155         }
42156         
42157         if (Ext.isString(store)) {
42158             // store id
42159             return this.get(store);
42160         } else {
42161             // store instance or store config
42162             return Ext.data.AbstractStore.create(store);
42163         }
42164     },
42165
42166     // getKey implementation for MixedCollection
42167     getKey : function(o) {
42168          return o.storeId;
42169     }
42170 }, function() {    
42171     /**
42172      * Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Mananger}. 
42173      * Sample usage:
42174      *
42175      *     Ext.regStore('AllUsers', {
42176      *         model: 'User'
42177      *     });
42178      *
42179      *     // the store can now easily be used throughout the application
42180      *     new Ext.List({
42181      *         store: 'AllUsers',
42182      *         ... other config
42183      *     });
42184      *
42185      * @param {String} id The id to set on the new store
42186      * @param {Object} config The store config
42187      * @member Ext
42188      * @method regStore
42189      */
42190     Ext.regStore = function(name, config) {
42191         var store;
42192
42193         if (Ext.isObject(name)) {
42194             config = name;
42195         } else {
42196             config.storeId = name;
42197         }
42198
42199         if (config instanceof Ext.data.Store) {
42200             store = config;
42201         } else {
42202             store = Ext.create('Ext.data.Store', config);
42203         }
42204
42205         return Ext.data.StoreManager.register(store);
42206     };
42207
42208     /**
42209      * Shortcut to {@link Ext.data.StoreManager#lookup}.
42210      * @member Ext
42211      * @method getStore
42212      * @alias Ext.data.StoreManager#lookup
42213      */
42214     Ext.getStore = function(name) {
42215         return Ext.data.StoreManager.lookup(name);
42216     };
42217 });
42218
42219 /**
42220  * Base class for all Ext components. All subclasses of Component may participate in the automated Ext component
42221  * lifecycle of creation, rendering and destruction which is provided by the {@link Ext.container.Container Container}
42222  * class. Components may be added to a Container through the {@link Ext.container.Container#items items} config option
42223  * at the time the Container is created, or they may be added dynamically via the
42224  * {@link Ext.container.Container#add add} method.
42225  *
42226  * The Component base class has built-in support for basic hide/show and enable/disable and size control behavior.
42227  *
42228  * All Components are registered with the {@link Ext.ComponentManager} on construction so that they can be referenced at
42229  * any time via {@link Ext#getCmp Ext.getCmp}, passing the {@link #id}.
42230  *
42231  * All user-developed visual widgets that are required to participate in automated lifecycle and size management should
42232  * subclass Component.
42233  *
42234  * See the [Creating new UI controls][1] tutorial for details on how and to either extend or augment ExtJs base classes
42235  * to create custom Components.
42236  *
42237  * Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the xtype
42238  * like {@link #getXType} and {@link #isXType}. See the [Component Guide][2] for more information on xtypes and the
42239  * Component hierarchy.
42240  *
42241  * This is the list of all valid xtypes:
42242  *
42243  *     xtype            Class
42244  *     -------------    ------------------
42245  *     button           {@link Ext.button.Button}
42246  *     buttongroup      {@link Ext.container.ButtonGroup}
42247  *     colorpalette     {@link Ext.picker.Color}
42248  *     component        {@link Ext.Component}
42249  *     container        {@link Ext.container.Container}
42250  *     cycle            {@link Ext.button.Cycle}
42251  *     dataview         {@link Ext.view.View}
42252  *     datepicker       {@link Ext.picker.Date}
42253  *     editor           {@link Ext.Editor}
42254  *     editorgrid       {@link Ext.grid.plugin.Editing}
42255  *     grid             {@link Ext.grid.Panel}
42256  *     multislider      {@link Ext.slider.Multi}
42257  *     panel            {@link Ext.panel.Panel}
42258  *     progressbar      {@link Ext.ProgressBar}
42259  *     slider           {@link Ext.slider.Single}
42260  *     splitbutton      {@link Ext.button.Split}
42261  *     tabpanel         {@link Ext.tab.Panel}
42262  *     treepanel        {@link Ext.tree.Panel}
42263  *     viewport         {@link Ext.container.Viewport}
42264  *     window           {@link Ext.window.Window}
42265  *
42266  *     Toolbar components
42267  *     ---------------------------------------
42268  *     pagingtoolbar    {@link Ext.toolbar.Paging}
42269  *     toolbar          {@link Ext.toolbar.Toolbar}
42270  *     tbfill           {@link Ext.toolbar.Fill}
42271  *     tbitem           {@link Ext.toolbar.Item}
42272  *     tbseparator      {@link Ext.toolbar.Separator}
42273  *     tbspacer         {@link Ext.toolbar.Spacer}
42274  *     tbtext           {@link Ext.toolbar.TextItem}
42275  *
42276  *     Menu components
42277  *     ---------------------------------------
42278  *     menu             {@link Ext.menu.Menu}
42279  *     menucheckitem    {@link Ext.menu.CheckItem}
42280  *     menuitem         {@link Ext.menu.Item}
42281  *     menuseparator    {@link Ext.menu.Separator}
42282  *     menutextitem     {@link Ext.menu.Item}
42283  *
42284  *     Form components
42285  *     ---------------------------------------
42286  *     form             {@link Ext.form.Panel}
42287  *     checkbox         {@link Ext.form.field.Checkbox}
42288  *     combo            {@link Ext.form.field.ComboBox}
42289  *     datefield        {@link Ext.form.field.Date}
42290  *     displayfield     {@link Ext.form.field.Display}
42291  *     field            {@link Ext.form.field.Base}
42292  *     fieldset         {@link Ext.form.FieldSet}
42293  *     hidden           {@link Ext.form.field.Hidden}
42294  *     htmleditor       {@link Ext.form.field.HtmlEditor}
42295  *     label            {@link Ext.form.Label}
42296  *     numberfield      {@link Ext.form.field.Number}
42297  *     radio            {@link Ext.form.field.Radio}
42298  *     radiogroup       {@link Ext.form.RadioGroup}
42299  *     textarea         {@link Ext.form.field.TextArea}
42300  *     textfield        {@link Ext.form.field.Text}
42301  *     timefield        {@link Ext.form.field.Time}
42302  *     trigger          {@link Ext.form.field.Trigger}
42303  *
42304  *     Chart components
42305  *     ---------------------------------------
42306  *     chart            {@link Ext.chart.Chart}
42307  *     barchart         {@link Ext.chart.series.Bar}
42308  *     columnchart      {@link Ext.chart.series.Column}
42309  *     linechart        {@link Ext.chart.series.Line}
42310  *     piechart         {@link Ext.chart.series.Pie}
42311  *
42312  * It should not usually be necessary to instantiate a Component because there are provided subclasses which implement
42313  * specialized Component use cases which cover most application needs. However it is possible to instantiate a base
42314  * Component, and it will be renderable, or will particpate in layouts as the child item of a Container:
42315  *
42316  *     @example
42317  *     Ext.create('Ext.Component', {
42318  *         html: 'Hello world!',
42319  *         width: 300,
42320  *         height: 200,
42321  *         padding: 20,
42322  *         style: {
42323  *             color: '#FFFFFF',
42324  *             backgroundColor:'#000000'
42325  *         },
42326  *         renderTo: Ext.getBody()
42327  *     });
42328  *
42329  * The Component above creates its encapsulating `div` upon render, and use the configured HTML as content. More complex
42330  * internal structure may be created using the {@link #renderTpl} configuration, although to display database-derived
42331  * mass data, it is recommended that an ExtJS data-backed Component such as a {@link Ext.view.View View}, or {@link
42332  * Ext.grid.Panel GridPanel}, or {@link Ext.tree.Panel TreePanel} be used.
42333  *
42334  * [1]: http://sencha.com/learn/Tutorial:Creating_new_UI_controls
42335  */
42336 Ext.define('Ext.Component', {
42337
42338     /* Begin Definitions */
42339
42340     alias: ['widget.component', 'widget.box'],
42341
42342     extend: 'Ext.AbstractComponent',
42343
42344     requires: [
42345         'Ext.util.DelayedTask'
42346     ],
42347
42348     uses: [
42349         'Ext.Layer',
42350         'Ext.resizer.Resizer',
42351         'Ext.util.ComponentDragger'
42352     ],
42353
42354     mixins: {
42355         floating: 'Ext.util.Floating'
42356     },
42357
42358     statics: {
42359         // Collapse/expand directions
42360         DIRECTION_TOP: 'top',
42361         DIRECTION_RIGHT: 'right',
42362         DIRECTION_BOTTOM: 'bottom',
42363         DIRECTION_LEFT: 'left',
42364
42365         VERTICAL_DIRECTION_Re: /^(?:top|bottom)$/,
42366
42367         // RegExp whih specifies characters in an xtype which must be translated to '-' when generating auto IDs.
42368         // This includes dot, comma and whitespace
42369         INVALID_ID_CHARS_Re: /[\.,\s]/g
42370     },
42371
42372     /* End Definitions */
42373
42374     /**
42375      * @cfg {Boolean/Object} resizable
42376      * Specify as `true` to apply a {@link Ext.resizer.Resizer Resizer} to this Component after rendering.
42377      *
42378      * May also be specified as a config object to be passed to the constructor of {@link Ext.resizer.Resizer Resizer}
42379      * to override any defaults. By default the Component passes its minimum and maximum size, and uses
42380      * `{@link Ext.resizer.Resizer#dynamic}: false`
42381      */
42382
42383     /**
42384      * @cfg {String} resizeHandles
42385      * A valid {@link Ext.resizer.Resizer} handles config string. Only applies when resizable = true.
42386      */
42387     resizeHandles: 'all',
42388
42389     /**
42390      * @cfg {Boolean} [autoScroll=false]
42391      * `true` to use overflow:'auto' on the components layout element and show scroll bars automatically when necessary,
42392      * `false` to clip any overflowing content.
42393      */
42394
42395     /**
42396      * @cfg {Boolean} floating
42397      * Specify as true to float the Component outside of the document flow using CSS absolute positioning.
42398      *
42399      * Components such as {@link Ext.window.Window Window}s and {@link Ext.menu.Menu Menu}s are floating by default.
42400      *
42401      * Floating Components that are programatically {@link Ext.Component#render rendered} will register themselves with
42402      * the global {@link Ext.WindowManager ZIndexManager}
42403      *
42404      * ### Floating Components as child items of a Container
42405      *
42406      * A floating Component may be used as a child item of a Container. This just allows the floating Component to seek
42407      * a ZIndexManager by examining the ownerCt chain.
42408      *
42409      * When configured as floating, Components acquire, at render time, a {@link Ext.ZIndexManager ZIndexManager} which
42410      * manages a stack of related floating Components. The ZIndexManager brings a single floating Component to the top
42411      * of its stack when the Component's {@link #toFront} method is called.
42412      *
42413      * The ZIndexManager is found by traversing up the {@link #ownerCt} chain to find an ancestor which itself is
42414      * floating. This is so that descendant floating Components of floating _Containers_ (Such as a ComboBox dropdown
42415      * within a Window) can have its zIndex managed relative to any siblings, but always **above** that floating
42416      * ancestor Container.
42417      *
42418      * If no floating ancestor is found, a floating Component registers itself with the default {@link Ext.WindowManager
42419      * ZIndexManager}.
42420      *
42421      * Floating components _do not participate in the Container's layout_. Because of this, they are not rendered until
42422      * you explicitly {@link #show} them.
42423      *
42424      * After rendering, the ownerCt reference is deleted, and the {@link #floatParent} property is set to the found
42425      * floating ancestor Container. If no floating ancestor Container was found the {@link #floatParent} property will
42426      * not be set.
42427      */
42428     floating: false,
42429
42430     /**
42431      * @cfg {Boolean} toFrontOnShow
42432      * True to automatically call {@link #toFront} when the {@link #show} method is called on an already visible,
42433      * floating component.
42434      */
42435     toFrontOnShow: true,
42436
42437     /**
42438      * @property {Ext.ZIndexManager} zIndexManager
42439      * Only present for {@link #floating} Components after they have been rendered.
42440      *
42441      * A reference to the ZIndexManager which is managing this Component's z-index.
42442      *
42443      * The {@link Ext.ZIndexManager ZIndexManager} maintains a stack of floating Component z-indices, and also provides
42444      * a single modal mask which is insert just beneath the topmost visible modal floating Component.
42445      *
42446      * Floating Components may be {@link #toFront brought to the front} or {@link #toBack sent to the back} of the
42447      * z-index stack.
42448      *
42449      * This defaults to the global {@link Ext.WindowManager ZIndexManager} for floating Components that are
42450      * programatically {@link Ext.Component#render rendered}.
42451      *
42452      * For {@link #floating} Components which are added to a Container, the ZIndexManager is acquired from the first
42453      * ancestor Container found which is floating, or if not found the global {@link Ext.WindowManager ZIndexManager} is
42454      * used.
42455      *
42456      * See {@link #floating} and {@link #floatParent}
42457      */
42458
42459     /**
42460      * @property {Ext.Container} floatParent
42461      * Only present for {@link #floating} Components which were inserted as descendant items of floating Containers.
42462      *
42463      * Floating Components that are programatically {@link Ext.Component#render rendered} will not have a `floatParent`
42464      * property.
42465      *
42466      * For {@link #floating} Components which are child items of a Container, the floatParent will be the floating
42467      * ancestor Container which is responsible for the base z-index value of all its floating descendants. It provides
42468      * a {@link Ext.ZIndexManager ZIndexManager} which provides z-indexing services for all its descendant floating
42469      * Components.
42470      *
42471      * For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the
42472      * Window as its `floatParent`
42473      *
42474      * See {@link #floating} and {@link #zIndexManager}
42475      */
42476
42477     /**
42478      * @cfg {Boolean/Object} [draggable=false]
42479      * Specify as true to make a {@link #floating} Component draggable using the Component's encapsulating element as
42480      * the drag handle.
42481      *
42482      * This may also be specified as a config object for the {@link Ext.util.ComponentDragger ComponentDragger} which is
42483      * instantiated to perform dragging.
42484      *
42485      * For example to create a Component which may only be dragged around using a certain internal element as the drag
42486      * handle, use the delegate option:
42487      *
42488      *     new Ext.Component({
42489      *         constrain: true,
42490      *         floating: true,
42491      *         style: {
42492      *             backgroundColor: '#fff',
42493      *             border: '1px solid black'
42494      *         },
42495      *         html: '<h1 style="cursor:move">The title</h1><p>The content</p>',
42496      *         draggable: {
42497      *             delegate: 'h1'
42498      *         }
42499      *     }).show();
42500      */
42501
42502     /**
42503      * @cfg {Boolean} [maintainFlex=false]
42504      * **Only valid when a sibling element of a {@link Ext.resizer.Splitter Splitter} within a
42505      * {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox} layout.**
42506      *
42507      * Specifies that if an immediate sibling Splitter is moved, the Component on the *other* side is resized, and this
42508      * Component maintains its configured {@link Ext.layout.container.Box#flex flex} value.
42509      */
42510
42511     hideMode: 'display',
42512     // Deprecate 5.0
42513     hideParent: false,
42514
42515     ariaRole: 'presentation',
42516
42517     bubbleEvents: [],
42518
42519     actionMode: 'el',
42520     monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
42521
42522     //renderTpl: new Ext.XTemplate(
42523     //    '<div id="{id}" class="{baseCls} {cls} {cmpCls}<tpl if="typeof ui !== \'undefined\'"> {uiBase}-{ui}</tpl>"<tpl if="typeof style !== \'undefined\'"> style="{style}"</tpl>></div>', {
42524     //        compiled: true,
42525     //        disableFormats: true
42526     //    }
42527     //),
42528
42529     /**
42530      * Creates new Component.
42531      * @param {Ext.Element/String/Object} config The configuration options may be specified as either:
42532      *
42533      * - **an element** : it is set as the internal element and its id used as the component id
42534      * - **a string** : it is assumed to be the id of an existing element and is used as the component id
42535      * - **anything else** : it is assumed to be a standard config object and is applied to the component
42536      */
42537     constructor: function(config) {
42538         var me = this;
42539
42540         config = config || {};
42541         if (config.initialConfig) {
42542
42543             // Being initialized from an Ext.Action instance...
42544             if (config.isAction) {
42545                 me.baseAction = config;
42546             }
42547             config = config.initialConfig;
42548             // component cloning / action set up
42549         }
42550         else if (config.tagName || config.dom || Ext.isString(config)) {
42551             // element object
42552             config = {
42553                 applyTo: config,
42554                 id: config.id || config
42555             };
42556         }
42557
42558         me.callParent([config]);
42559
42560         // If we were configured from an instance of Ext.Action, (or configured with a baseAction option),
42561         // register this Component as one of its items
42562         if (me.baseAction){
42563             me.baseAction.addComponent(me);
42564         }
42565     },
42566
42567     /**
42568      * The initComponent template method is an important initialization step for a Component. It is intended to be
42569      * implemented by each subclass of Ext.Component to provide any needed constructor logic. The
42570      * initComponent method of the class being created is called first, with each initComponent method
42571      * up the hierarchy to Ext.Component being called thereafter. This makes it easy to implement and,
42572      * if needed, override the constructor logic of the Component at any step in the hierarchy.
42573      *
42574      * The initComponent method **must** contain a call to {@link Ext.Base#callParent callParent} in order
42575      * to ensure that the parent class' initComponent method is also called.
42576      *
42577      * The following example demonstrates using a dynamic string for the text of a button at the time of
42578      * instantiation of the class.
42579      *
42580      *     Ext.define('DynamicButtonText', {
42581      *         extend: 'Ext.button.Button',
42582      *
42583      *         initComponent: function() {
42584      *             this.text = new Date();
42585      *             this.renderTo = Ext.getBody();
42586      *             this.callParent();
42587      *         }
42588      *     });
42589      *
42590      *     Ext.onReady(function() {
42591      *         Ext.create('DynamicButtonText');
42592      *     });
42593      *
42594      * @template
42595      */
42596     initComponent: function() {
42597         var me = this;
42598
42599         me.callParent();
42600
42601         if (me.listeners) {
42602             me.on(me.listeners);
42603             delete me.listeners;
42604         }
42605         me.enableBubble(me.bubbleEvents);
42606         me.mons = [];
42607     },
42608
42609     // private
42610     afterRender: function() {
42611         var me = this,
42612             resizable = me.resizable;
42613
42614         if (me.floating) {
42615             me.makeFloating(me.floating);
42616         } else {
42617             me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
42618         }
42619
42620         if (Ext.isDefined(me.autoScroll)) {
42621             me.setAutoScroll(me.autoScroll);
42622         }
42623         me.callParent();
42624
42625         if (!(me.x && me.y) && (me.pageX || me.pageY)) {
42626             me.setPagePosition(me.pageX, me.pageY);
42627         }
42628
42629         if (resizable) {
42630             me.initResizable(resizable);
42631         }
42632
42633         if (me.draggable) {
42634             me.initDraggable();
42635         }
42636
42637         me.initAria();
42638     },
42639
42640     initAria: function() {
42641         var actionEl = this.getActionEl(),
42642             role = this.ariaRole;
42643         if (role) {
42644             actionEl.dom.setAttribute('role', role);
42645         }
42646     },
42647
42648     /**
42649      * Sets the overflow on the content element of the component.
42650      * @param {Boolean} scroll True to allow the Component to auto scroll.
42651      * @return {Ext.Component} this
42652      */
42653     setAutoScroll : function(scroll){
42654         var me = this,
42655             targetEl;
42656         scroll = !!scroll;
42657         if (me.rendered) {
42658             targetEl = me.getTargetEl();
42659             targetEl.setStyle('overflow', scroll ? 'auto' : '');
42660             if (scroll && (Ext.isIE6 || Ext.isIE7)) {
42661                 // The scrollable container element must be non-statically positioned or IE6/7 will make
42662                 // positioned children stay in place rather than scrolling with the rest of the content
42663                 targetEl.position();
42664             }
42665         }
42666         me.autoScroll = scroll;
42667         return me;
42668     },
42669
42670     // private
42671     makeFloating : function(cfg){
42672         this.mixins.floating.constructor.call(this, cfg);
42673     },
42674
42675     initResizable: function(resizable) {
42676         var me = this;
42677
42678         resizable = Ext.apply({
42679             target: me,
42680             dynamic: false,
42681             constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent()),
42682             handles: me.resizeHandles
42683         }, resizable);
42684         resizable.target = me;
42685         me.resizer = Ext.create('Ext.resizer.Resizer', resizable);
42686     },
42687
42688     getDragEl: function() {
42689         return this.el;
42690     },
42691
42692     initDraggable: function() {
42693         var me = this,
42694             ddConfig = Ext.applyIf({
42695                 el: me.getDragEl(),
42696                 constrainTo: me.constrain ? (me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent())) : undefined
42697             }, me.draggable);
42698
42699         // Add extra configs if Component is specified to be constrained
42700         if (me.constrain || me.constrainDelegate) {
42701             ddConfig.constrain = me.constrain;
42702             ddConfig.constrainDelegate = me.constrainDelegate;
42703         }
42704
42705         me.dd = Ext.create('Ext.util.ComponentDragger', me, ddConfig);
42706     },
42707
42708     /**
42709      * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}. This
42710      * method fires the {@link #move} event.
42711      * @param {Number} left The new left
42712      * @param {Number} top The new top
42713      * @param {Boolean/Object} [animate] If true, the Component is _animated_ into its new position. You may also pass an
42714      * animation configuration.
42715      * @return {Ext.Component} this
42716      */
42717     setPosition: function(x, y, animate) {
42718         var me = this,
42719             el = me.el,
42720             to = {},
42721             adj, adjX, adjY, xIsNumber, yIsNumber;
42722
42723         if (Ext.isArray(x)) {
42724             animate = y;
42725             y = x[1];
42726             x = x[0];
42727         }
42728         me.x = x;
42729         me.y = y;
42730
42731         if (!me.rendered) {
42732             return me;
42733         }
42734
42735         adj = me.adjustPosition(x, y);
42736         adjX = adj.x;
42737         adjY = adj.y;
42738         xIsNumber = Ext.isNumber(adjX);
42739         yIsNumber = Ext.isNumber(adjY);
42740
42741         if (xIsNumber || yIsNumber) {
42742             if (animate) {
42743                 if (xIsNumber) {
42744                     to.left = adjX;
42745                 }
42746                 if (yIsNumber) {
42747                     to.top = adjY;
42748                 }
42749
42750                 me.stopAnimation();
42751                 me.animate(Ext.apply({
42752                     duration: 1000,
42753                     listeners: {
42754                         afteranimate: Ext.Function.bind(me.afterSetPosition, me, [adjX, adjY])
42755                     },
42756                     to: to
42757                 }, animate));
42758             }
42759             else {
42760                 if (!xIsNumber) {
42761                     el.setTop(adjY);
42762                 }
42763                 else if (!yIsNumber) {
42764                     el.setLeft(adjX);
42765                 }
42766                 else {
42767                     el.setLeftTop(adjX, adjY);
42768                 }
42769                 me.afterSetPosition(adjX, adjY);
42770             }
42771         }
42772         return me;
42773     },
42774
42775     /**
42776      * @private
42777      * @template
42778      * Template method called after a Component has been positioned.
42779      */
42780     afterSetPosition: function(ax, ay) {
42781         this.onPosition(ax, ay);
42782         this.fireEvent('move', this, ax, ay);
42783     },
42784
42785     /**
42786      * Displays component at specific xy position.
42787      * A floating component (like a menu) is positioned relative to its ownerCt if any.
42788      * Useful for popping up a context menu:
42789      *
42790      *     listeners: {
42791      *         itemcontextmenu: function(view, record, item, index, event, options) {
42792      *             Ext.create('Ext.menu.Menu', {
42793      *                 width: 100,
42794      *                 height: 100,
42795      *                 margin: '0 0 10 0',
42796      *                 items: [{
42797      *                     text: 'regular item 1'
42798      *                 },{
42799      *                     text: 'regular item 2'
42800      *                 },{
42801      *                     text: 'regular item 3'
42802      *                 }]
42803      *             }).showAt(event.getXY());
42804      *         }
42805      *     }
42806      *
42807      * @param {Number} x The new x position
42808      * @param {Number} y The new y position
42809      * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
42810      * animation configuration.
42811      */
42812     showAt: function(x, y, animate) {
42813         var me = this;
42814
42815         if (me.floating) {
42816             me.setPosition(x, y, animate);
42817         } else {
42818             me.setPagePosition(x, y, animate);
42819         }
42820         me.show();
42821     },
42822
42823     /**
42824      * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
42825      * This method fires the {@link #move} event.
42826      * @param {Number} x The new x position
42827      * @param {Number} y The new y position
42828      * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
42829      * animation configuration.
42830      * @return {Ext.Component} this
42831      */
42832     setPagePosition: function(x, y, animate) {
42833         var me = this,
42834             p;
42835
42836         if (Ext.isArray(x)) {
42837             y = x[1];
42838             x = x[0];
42839         }
42840         me.pageX = x;
42841         me.pageY = y;
42842         if (me.floating && me.floatParent) {
42843             // Floating Components being positioned in their ownerCt have to be made absolute
42844             p = me.floatParent.getTargetEl().getViewRegion();
42845             if (Ext.isNumber(x) && Ext.isNumber(p.left)) {
42846                 x -= p.left;
42847             }
42848             if (Ext.isNumber(y) && Ext.isNumber(p.top)) {
42849                 y -= p.top;
42850             }
42851             me.setPosition(x, y, animate);
42852         }
42853         else {
42854             p = me.el.translatePoints(x, y);
42855             me.setPosition(p.left, p.top, animate);
42856         }
42857         return me;
42858     },
42859
42860     /**
42861      * Gets the current box measurements of the component's underlying element.
42862      * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
42863      * @return {Object} box An object in the format {x, y, width, height}
42864      */
42865     getBox : function(local){
42866         var pos = this.getPosition(local),
42867             size = this.getSize();
42868
42869         size.x = pos[0];
42870         size.y = pos[1];
42871         return size;
42872     },
42873
42874     /**
42875      * Sets the current box measurements of the component's underlying element.
42876      * @param {Object} box An object in the format {x, y, width, height}
42877      * @return {Ext.Component} this
42878      */
42879     updateBox : function(box){
42880         this.setSize(box.width, box.height);
42881         this.setPagePosition(box.x, box.y);
42882         return this;
42883     },
42884
42885     // Include margins
42886     getOuterSize: function() {
42887         var el = this.el;
42888         return {
42889             width: el.getWidth() + el.getMargin('lr'),
42890             height: el.getHeight() + el.getMargin('tb')
42891         };
42892     },
42893
42894     // private
42895     adjustPosition: function(x, y) {
42896
42897         // Floating Components being positioned in their ownerCt have to be made absolute
42898         if (this.floating && this.floatParent) {
42899             var o = this.floatParent.getTargetEl().getViewRegion();
42900             x += o.left;
42901             y += o.top;
42902         }
42903
42904         return {
42905             x: x,
42906             y: y
42907         };
42908     },
42909
42910     /**
42911      * Gets the current XY position of the component's underlying element.
42912      * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
42913      * @return {Number[]} The XY position of the element (e.g., [100, 200])
42914      */
42915     getPosition: function(local) {
42916         var me = this,
42917             el = me.el,
42918             xy,
42919             o;
42920
42921         // Floating Components which were just rendered with no ownerCt return local position.
42922         if ((local === true) || (me.floating && !me.floatParent)) {
42923             return [el.getLeft(true), el.getTop(true)];
42924         }
42925         xy = me.xy || el.getXY();
42926
42927         // Floating Components in an ownerCt have to have their positions made relative
42928         if (me.floating) {
42929             o = me.floatParent.getTargetEl().getViewRegion();
42930             xy[0] -= o.left;
42931             xy[1] -= o.top;
42932         }
42933         return xy;
42934     },
42935
42936     getId: function() {
42937         var me = this,
42938             xtype;
42939
42940         if (!me.id) {
42941             xtype = me.getXType();
42942             xtype = xtype ? xtype.replace(Ext.Component.INVALID_ID_CHARS_Re, '-') : 'ext-comp';
42943             me.id = xtype + '-' + me.getAutoId();
42944         }
42945         return me.id;
42946     },
42947
42948     onEnable: function() {
42949         var actionEl = this.getActionEl();
42950         actionEl.dom.removeAttribute('aria-disabled');
42951         actionEl.dom.disabled = false;
42952         this.callParent();
42953     },
42954
42955     onDisable: function() {
42956         var actionEl = this.getActionEl();
42957         actionEl.dom.setAttribute('aria-disabled', true);
42958         actionEl.dom.disabled = true;
42959         this.callParent();
42960     },
42961
42962     /**
42963      * Shows this Component, rendering it first if {@link #autoRender} or {@link #floating} are `true`.
42964      *
42965      * After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and
42966      * brought to the front of its {@link #zIndexManager z-index stack}.
42967      *
42968      * @param {String/Ext.Element} [animateTarget=null] **only valid for {@link #floating} Components such as {@link
42969      * Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
42970      * with `floating: true`.** The target from which the Component should animate from while opening.
42971      * @param {Function} [callback] A callback function to call after the Component is displayed.
42972      * Only necessary if animation was specified.
42973      * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
42974      * Defaults to this Component.
42975      * @return {Ext.Component} this
42976      */
42977     show: function(animateTarget, cb, scope) {
42978         var me = this;
42979
42980         if (me.rendered && me.isVisible()) {
42981             if (me.toFrontOnShow && me.floating) {
42982                 me.toFront();
42983             }
42984         } else if (me.fireEvent('beforeshow', me) !== false) {
42985             me.hidden = false;
42986
42987             // Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
42988             if (!me.rendered && (me.autoRender || me.floating)) {
42989                 me.doAutoRender();
42990             }
42991             if (me.rendered) {
42992                 me.beforeShow();
42993                 me.onShow.apply(me, arguments);
42994
42995                 // Notify any owning Container unless it's suspended.
42996                 // Floating Components do not participate in layouts.
42997                 if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
42998                     me.ownerCt.doLayout();
42999                 }
43000                 me.afterShow.apply(me, arguments);
43001             }
43002         }
43003         return me;
43004     },
43005
43006     beforeShow: Ext.emptyFn,
43007
43008     // Private. Override in subclasses where more complex behaviour is needed.
43009     onShow: function() {
43010         var me = this;
43011
43012         me.el.show();
43013         me.callParent(arguments);
43014         if (me.floating && me.constrain) {
43015             me.doConstrain();
43016         }
43017     },
43018
43019     afterShow: function(animateTarget, cb, scope) {
43020         var me = this,
43021             fromBox,
43022             toBox,
43023             ghostPanel;
43024
43025         // Default to configured animate target if none passed
43026         animateTarget = animateTarget || me.animateTarget;
43027
43028         // Need to be able to ghost the Component
43029         if (!me.ghost) {
43030             animateTarget = null;
43031         }
43032         // If we're animating, kick of an animation of the ghost from the target to the *Element* current box
43033         if (animateTarget) {
43034             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
43035             toBox = me.el.getBox();
43036             fromBox = animateTarget.getBox();
43037             me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
43038             ghostPanel = me.ghost();
43039             ghostPanel.el.stopAnimation();
43040
43041             // Shunting it offscreen immediately, *before* the Animation class grabs it ensure no flicker.
43042             ghostPanel.el.setX(-10000);
43043
43044             ghostPanel.el.animate({
43045                 from: fromBox,
43046                 to: toBox,
43047                 listeners: {
43048                     afteranimate: function() {
43049                         delete ghostPanel.componentLayout.lastComponentSize;
43050                         me.unghost();
43051                         me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
43052                         me.onShowComplete(cb, scope);
43053                     }
43054                 }
43055             });
43056         }
43057         else {
43058             me.onShowComplete(cb, scope);
43059         }
43060     },
43061
43062     onShowComplete: function(cb, scope) {
43063         var me = this;
43064         if (me.floating) {
43065             me.toFront();
43066         }
43067         Ext.callback(cb, scope || me);
43068         me.fireEvent('show', me);
43069     },
43070
43071     /**
43072      * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
43073      * @param {String/Ext.Element/Ext.Component} [animateTarget=null] **only valid for {@link #floating} Components
43074      * such as {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have
43075      * been configured with `floating: true`.**. The target to which the Component should animate while hiding.
43076      * @param {Function} [callback] A callback function to call after the Component is hidden.
43077      * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
43078      * Defaults to this Component.
43079      * @return {Ext.Component} this
43080      */
43081     hide: function() {
43082         var me = this;
43083
43084         // Clear the flag which is set if a floatParent was hidden while this is visible.
43085         // If a hide operation was subsequently called, that pending show must be hidden.
43086         me.showOnParentShow = false;
43087
43088         if (!(me.rendered && !me.isVisible()) && me.fireEvent('beforehide', me) !== false) {
43089             me.hidden = true;
43090             if (me.rendered) {
43091                 me.onHide.apply(me, arguments);
43092
43093                 // Notify any owning Container unless it's suspended.
43094                 // Floating Components do not participate in layouts.
43095                 if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
43096                     me.ownerCt.doLayout();
43097                 }
43098             }
43099         }
43100         return me;
43101     },
43102
43103     // Possibly animate down to a target element.
43104     onHide: function(animateTarget, cb, scope) {
43105         var me = this,
43106             ghostPanel,
43107             toBox;
43108
43109         // Default to configured animate target if none passed
43110         animateTarget = animateTarget || me.animateTarget;
43111
43112         // Need to be able to ghost the Component
43113         if (!me.ghost) {
43114             animateTarget = null;
43115         }
43116         // If we're animating, kick off an animation of the ghost down to the target
43117         if (animateTarget) {
43118             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
43119             ghostPanel = me.ghost();
43120             ghostPanel.el.stopAnimation();
43121             toBox = animateTarget.getBox();
43122             toBox.width += 'px';
43123             toBox.height += 'px';
43124             ghostPanel.el.animate({
43125                 to: toBox,
43126                 listeners: {
43127                     afteranimate: function() {
43128                         delete ghostPanel.componentLayout.lastComponentSize;
43129                         ghostPanel.el.hide();
43130                         me.afterHide(cb, scope);
43131                     }
43132                 }
43133             });
43134         }
43135         me.el.hide();
43136         if (!animateTarget) {
43137             me.afterHide(cb, scope);
43138         }
43139     },
43140
43141     afterHide: function(cb, scope) {
43142         Ext.callback(cb, scope || this);
43143         this.fireEvent('hide', this);
43144     },
43145
43146     /**
43147      * @private
43148      * @template
43149      * Template method to contribute functionality at destroy time.
43150      */
43151     onDestroy: function() {
43152         var me = this;
43153
43154         // Ensure that any ancillary components are destroyed.
43155         if (me.rendered) {
43156             Ext.destroy(
43157                 me.proxy,
43158                 me.proxyWrap,
43159                 me.resizer
43160             );
43161             // Different from AbstractComponent
43162             if (me.actionMode == 'container' || me.removeMode == 'container') {
43163                 me.container.remove();
43164             }
43165         }
43166         delete me.focusTask;
43167         me.callParent();
43168     },
43169
43170     deleteMembers: function() {
43171         var args = arguments,
43172             len = args.length,
43173             i = 0;
43174         for (; i < len; ++i) {
43175             delete this[args[i]];
43176         }
43177     },
43178
43179     /**
43180      * Try to focus this component.
43181      * @param {Boolean} [selectText] If applicable, true to also select the text in this component
43182      * @param {Boolean/Number} [delay] Delay the focus this number of milliseconds (true for 10 milliseconds).
43183      * @return {Ext.Component} this
43184      */
43185     focus: function(selectText, delay) {
43186         var me = this,
43187                 focusEl;
43188
43189         if (delay) {
43190             if (!me.focusTask) {
43191                 me.focusTask = Ext.create('Ext.util.DelayedTask', me.focus);
43192             }
43193             me.focusTask.delay(Ext.isNumber(delay) ? delay : 10, null, me, [selectText, false]);
43194             return me;
43195         }
43196
43197         if (me.rendered && !me.isDestroyed) {
43198             // getFocusEl could return a Component.
43199             focusEl = me.getFocusEl();
43200             focusEl.focus();
43201             if (focusEl.dom && selectText === true) {
43202                 focusEl.dom.select();
43203             }
43204
43205             // Focusing a floating Component brings it to the front of its stack.
43206             // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.
43207             if (me.floating) {
43208                 me.toFront(true);
43209             }
43210         }
43211         return me;
43212     },
43213
43214     /**
43215      * @private
43216      * Returns the focus holder element associated with this Component. By default, this is the Component's encapsulating
43217      * element. Subclasses which use embedded focusable elements (such as Window and Button) should override this for use
43218      * by the {@link #focus} method.
43219      * @returns {Ext.Element} the focus holing element.
43220      */
43221     getFocusEl: function() {
43222         return this.el;
43223     },
43224
43225     // private
43226     blur: function() {
43227         if (this.rendered) {
43228             this.getFocusEl().blur();
43229         }
43230         return this;
43231     },
43232
43233     getEl: function() {
43234         return this.el;
43235     },
43236
43237     // Deprecate 5.0
43238     getResizeEl: function() {
43239         return this.el;
43240     },
43241
43242     // Deprecate 5.0
43243     getPositionEl: function() {
43244         return this.el;
43245     },
43246
43247     // Deprecate 5.0
43248     getActionEl: function() {
43249         return this.el;
43250     },
43251
43252     // Deprecate 5.0
43253     getVisibilityEl: function() {
43254         return this.el;
43255     },
43256
43257     // Deprecate 5.0
43258     onResize: Ext.emptyFn,
43259
43260     // private
43261     getBubbleTarget: function() {
43262         return this.ownerCt;
43263     },
43264
43265     // private
43266     getContentTarget: function() {
43267         return this.el;
43268     },
43269
43270     /**
43271      * Clone the current component using the original config values passed into this instance by default.
43272      * @param {Object} overrides A new config containing any properties to override in the cloned version.
43273      * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
43274      * @return {Ext.Component} clone The cloned copy of this component
43275      */
43276     cloneConfig: function(overrides) {
43277         overrides = overrides || {};
43278         var id = overrides.id || Ext.id(),
43279             cfg = Ext.applyIf(overrides, this.initialConfig),
43280             self;
43281
43282         cfg.id = id;
43283
43284         self = Ext.getClass(this);
43285
43286         // prevent dup id
43287         return new self(cfg);
43288     },
43289
43290     /**
43291      * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all available
43292      * xtypes, see the {@link Ext.Component} header. Example usage:
43293      *
43294      *     var t = new Ext.form.field.Text();
43295      *     alert(t.getXType());  // alerts 'textfield'
43296      *
43297      * @return {String} The xtype
43298      */
43299     getXType: function() {
43300         return this.self.xtype;
43301     },
43302
43303     /**
43304      * Find a container above this component at any level by a custom function. If the passed function returns true, the
43305      * container will be returned.
43306      * @param {Function} fn The custom function to call with the arguments (container, this component).
43307      * @return {Ext.container.Container} The first Container for which the custom function returns true
43308      */
43309     findParentBy: function(fn) {
43310         var p;
43311
43312         // Iterate up the ownerCt chain until there's no ownerCt, or we find an ancestor which matches using the selector function.
43313         for (p = this.ownerCt; p && !fn(p, this); p = p.ownerCt);
43314         return p || null;
43315     },
43316
43317     /**
43318      * Find a container above this component at any level by xtype or class
43319      *
43320      * See also the {@link Ext.Component#up up} method.
43321      *
43322      * @param {String/Ext.Class} xtype The xtype string for a component, or the class of the component directly
43323      * @return {Ext.container.Container} The first Container which matches the given xtype or class
43324      */
43325     findParentByType: function(xtype) {
43326         return Ext.isFunction(xtype) ?
43327             this.findParentBy(function(p) {
43328                 return p.constructor === xtype;
43329             })
43330         :
43331             this.up(xtype);
43332     },
43333
43334     /**
43335      * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope
43336      * (*this*) of function call will be the scope provided or the current component. The arguments to the function will
43337      * be the args provided or the current component. If the function returns false at any point, the bubble is stopped.
43338      *
43339      * @param {Function} fn The function to call
43340      * @param {Object} [scope] The scope of the function. Defaults to current node.
43341      * @param {Array} [args] The args to call the function with. Defaults to passing the current component.
43342      * @return {Ext.Component} this
43343      */
43344     bubble: function(fn, scope, args) {
43345         var p = this;
43346         while (p) {
43347             if (fn.apply(scope || p, args || [p]) === false) {
43348                 break;
43349             }
43350             p = p.ownerCt;
43351         }
43352         return this;
43353     },
43354
43355     getProxy: function() {
43356         var me = this,
43357             target;
43358
43359         if (!me.proxy) {
43360             target = Ext.getBody();
43361             if (Ext.scopeResetCSS) {
43362                 me.proxyWrap = target = Ext.getBody().createChild({
43363                     cls: Ext.baseCSSPrefix + 'reset'
43364                 });
43365             }
43366             me.proxy = me.el.createProxy(Ext.baseCSSPrefix + 'proxy-el', target, true);
43367         }
43368         return me.proxy;
43369     }
43370
43371 });
43372
43373 /**
43374  * @class Ext.layout.container.AbstractContainer
43375  * @extends Ext.layout.Layout
43376  * Please refer to sub classes documentation
43377  * @private
43378  */
43379 Ext.define('Ext.layout.container.AbstractContainer', {
43380
43381     /* Begin Definitions */
43382
43383     extend: 'Ext.layout.Layout',
43384
43385     /* End Definitions */
43386
43387     type: 'container',
43388
43389     /**
43390      * @cfg {Boolean} bindToOwnerCtComponent
43391      * Flag to notify the ownerCt Component on afterLayout of a change
43392      */
43393     bindToOwnerCtComponent: false,
43394
43395     /**
43396      * @cfg {Boolean} bindToOwnerCtContainer
43397      * Flag to notify the ownerCt Container on afterLayout of a change
43398      */
43399     bindToOwnerCtContainer: false,
43400
43401     /**
43402      * @cfg {String} itemCls
43403      * <p>An optional extra CSS class that will be added to the container. This can be useful for adding
43404      * customized styles to the container or any of its children using standard CSS rules. See
43405      * {@link Ext.Component}.{@link Ext.Component#componentCls componentCls} also.</p>
43406      * </p>
43407      */
43408
43409     /**
43410     * Set the size of an item within the Container.  We should always use setCalculatedSize.
43411     * @private
43412     */
43413     setItemSize: function(item, width, height) {
43414         if (Ext.isObject(width)) {
43415             height = width.height;
43416             width = width.width;
43417         }
43418         item.setCalculatedSize(width, height, this.owner);
43419     },
43420
43421     /**
43422      * <p>Returns an array of child components either for a render phase (Performed in the beforeLayout method of the layout's
43423      * base class), or the layout phase (onLayout).</p>
43424      * @return {Ext.Component[]} of child components
43425      */
43426     getLayoutItems: function() {
43427         return this.owner && this.owner.items && this.owner.items.items || [];
43428     },
43429
43430     /**
43431      * Containers should not lay out child components when collapsed.
43432      */
43433     beforeLayout: function() {
43434         return !this.owner.collapsed && this.callParent(arguments);
43435     },
43436
43437     afterLayout: function() {
43438         this.owner.afterLayout(this);
43439     },
43440     /**
43441      * Returns the owner component's resize element.
43442      * @return {Ext.Element}
43443      */
43444      getTarget: function() {
43445          return this.owner.getTargetEl();
43446      },
43447     /**
43448      * <p>Returns the element into which rendering must take place. Defaults to the owner Container's target element.</p>
43449      * May be overridden in layout managers which implement an inner element.
43450      * @return {Ext.Element}
43451      */
43452      getRenderTarget: function() {
43453          return this.owner.getTargetEl();
43454      }
43455 });
43456
43457 /**
43458 * @class Ext.layout.container.Container
43459 * @extends Ext.layout.container.AbstractContainer
43460 * <p>This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
43461 * configuration property.  See {@link Ext.container.Container#layout} for additional details.</p>
43462 */
43463 Ext.define('Ext.layout.container.Container', {
43464
43465     /* Begin Definitions */
43466
43467     extend: 'Ext.layout.container.AbstractContainer',
43468     alternateClassName: 'Ext.layout.ContainerLayout',
43469
43470     /* End Definitions */
43471
43472     layoutItem: function(item, box) {
43473         if (box) {
43474             item.doComponentLayout(box.width, box.height);
43475         } else {
43476             item.doComponentLayout();
43477         }
43478     },
43479
43480     getLayoutTargetSize : function() {
43481         var target = this.getTarget(),
43482             ret;
43483
43484         if (target) {
43485             ret = target.getViewSize();
43486
43487             // IE in will sometimes return a width of 0 on the 1st pass of getViewSize.
43488             // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
43489             // with getViewSize
43490             if (Ext.isIE && ret.width == 0){
43491                 ret = target.getStyleSize();
43492             }
43493
43494             ret.width -= target.getPadding('lr');
43495             ret.height -= target.getPadding('tb');
43496         }
43497         return ret;
43498     },
43499
43500     beforeLayout: function() {
43501         if (this.owner.beforeLayout(arguments) !== false) {
43502             return this.callParent(arguments);
43503         }
43504         else {
43505             return false;
43506         }
43507     },
43508
43509     /**
43510      * @protected
43511      * Returns all items that are rendered
43512      * @return {Array} All matching items
43513      */
43514     getRenderedItems: function() {
43515         var me = this,
43516             target = me.getTarget(),
43517             items = me.getLayoutItems(),
43518             ln = items.length,
43519             renderedItems = [],
43520             i, item;
43521
43522         for (i = 0; i < ln; i++) {
43523             item = items[i];
43524             if (item.rendered && me.isValidParent(item, target, i)) {
43525                 renderedItems.push(item);
43526             }
43527         }
43528
43529         return renderedItems;
43530     },
43531
43532     /**
43533      * @protected
43534      * Returns all items that are both rendered and visible
43535      * @return {Array} All matching items
43536      */
43537     getVisibleItems: function() {
43538         var target   = this.getTarget(),
43539             items = this.getLayoutItems(),
43540             ln = items.length,
43541             visibleItems = [],
43542             i, item;
43543
43544         for (i = 0; i < ln; i++) {
43545             item = items[i];
43546             if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) {
43547                 visibleItems.push(item);
43548             }
43549         }
43550
43551         return visibleItems;
43552     }
43553 });
43554 /**
43555  * @class Ext.layout.container.Auto
43556  * @extends Ext.layout.container.Container
43557  *
43558  * The AutoLayout is the default layout manager delegated by {@link Ext.container.Container} to
43559  * render any child Components when no `{@link Ext.container.Container#layout layout}` is configured into
43560  * a `{@link Ext.container.Container Container}.` AutoLayout provides only a passthrough of any layout calls
43561  * to any child containers.
43562  *
43563  *     @example
43564  *     Ext.create('Ext.Panel', {
43565  *         width: 500,
43566  *         height: 280,
43567  *         title: "AutoLayout Panel",
43568  *         layout: 'auto',
43569  *         renderTo: document.body,
43570  *         items: [{
43571  *             xtype: 'panel',
43572  *             title: 'Top Inner Panel',
43573  *             width: '75%',
43574  *             height: 90
43575  *         },
43576  *         {
43577  *             xtype: 'panel',
43578  *             title: 'Bottom Inner Panel',
43579  *             width: '75%',
43580  *             height: 90
43581  *         }]
43582  *     });
43583  */
43584 Ext.define('Ext.layout.container.Auto', {
43585
43586     /* Begin Definitions */
43587
43588     alias: ['layout.auto', 'layout.autocontainer'],
43589
43590     extend: 'Ext.layout.container.Container',
43591
43592     /* End Definitions */
43593
43594     type: 'autocontainer',
43595
43596     bindToOwnerCtComponent: true,
43597
43598     // @private
43599     onLayout : function(owner, target) {
43600         var me = this,
43601             items = me.getLayoutItems(),
43602             ln = items.length,
43603             i;
43604
43605         // Ensure the Container is only primed with the clear element if there are child items.
43606         if (ln) {
43607             // Auto layout uses natural HTML flow to arrange the child items.
43608             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
43609             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
43610             if (!me.clearEl) {
43611                 me.clearEl = me.getRenderTarget().createChild({
43612                     cls: Ext.baseCSSPrefix + 'clear',
43613                     role: 'presentation'
43614                 });
43615             }
43616
43617             // Auto layout allows CSS to size its child items.
43618             for (i = 0; i < ln; i++) {
43619                 me.setItemSize(items[i]);
43620             }
43621         }
43622     },
43623
43624     configureItem: function(item) {
43625         this.callParent(arguments);
43626
43627         // Auto layout does not manage any dimensions.
43628         item.layoutManagedHeight = 2;
43629         item.layoutManagedWidth = 2;
43630     }
43631 });
43632 /**
43633  * @class Ext.container.AbstractContainer
43634  * @extends Ext.Component
43635  * An abstract base class which provides shared methods for Containers across the Sencha product line.
43636  * @private
43637  */
43638 Ext.define('Ext.container.AbstractContainer', {
43639
43640     /* Begin Definitions */
43641
43642     extend: 'Ext.Component',
43643
43644     requires: [
43645         'Ext.util.MixedCollection',
43646         'Ext.layout.container.Auto',
43647         'Ext.ZIndexManager'
43648     ],
43649
43650     /* End Definitions */
43651     /**
43652      * @cfg {String/Object} layout
43653      * <p><b>Important</b>: In order for child items to be correctly sized and
43654      * positioned, typically a layout manager <b>must</b> be specified through
43655      * the <code>layout</code> configuration option.</p>
43656      * <p>The sizing and positioning of child {@link #items} is the responsibility of
43657      * the Container's layout manager which creates and manages the type of layout
43658      * you have in mind.  For example:</p>
43659      * <p>If the {@link #layout} configuration is not explicitly specified for
43660      * a general purpose container (e.g. Container or Panel) the
43661      * {@link Ext.layout.container.Auto default layout manager} will be used
43662      * which does nothing but render child components sequentially into the
43663      * Container (no sizing or positioning will be performed in this situation).</p>
43664      * <p><b><code>layout</code></b> may be specified as either as an Object or as a String:</p>
43665      * <div><ul class="mdetail-params">
43666      * <li><u>Specify as an Object</u></li>
43667      * <div><ul class="mdetail-params">
43668      * <li>Example usage:</li>
43669      * <pre><code>
43670 layout: {
43671     type: 'vbox',
43672     align: 'left'
43673 }
43674        </code></pre>
43675      *
43676      * <li><code><b>type</b></code></li>
43677      * <br/><p>The layout type to be used for this container.  If not specified,
43678      * a default {@link Ext.layout.container.Auto} will be created and used.</p>
43679      * <p>Valid layout <code>type</code> values are:</p>
43680      * <div class="sub-desc"><ul class="mdetail-params">
43681      * <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> &nbsp;&nbsp;&nbsp; <b>Default</b></li>
43682      * <li><code><b>{@link Ext.layout.container.Card card}</b></code></li>
43683      * <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li>
43684      * <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li>
43685      * <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li>
43686      * <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li>
43687      * <li><code><b>{@link Ext.layout.container.Table table}</b></code></li>
43688      * </ul></div>
43689      *
43690      * <li>Layout specific configuration properties</li>
43691      * <p>Additional layout specific configuration properties may also be
43692      * specified. For complete details regarding the valid config options for
43693      * each layout type, see the layout class corresponding to the <code>type</code>
43694      * specified.</p>
43695      *
43696      * </ul></div>
43697      *
43698      * <li><u>Specify as a String</u></li>
43699      * <div><ul class="mdetail-params">
43700      * <li>Example usage:</li>
43701      * <pre><code>
43702 layout: 'vbox'
43703        </code></pre>
43704      * <li><code><b>layout</b></code></li>
43705      * <p>The layout <code>type</code> to be used for this container (see list
43706      * of valid layout type values above).</p>
43707      * <p>Additional layout specific configuration properties. For complete
43708      * details regarding the valid config options for each layout type, see the
43709      * layout class corresponding to the <code>layout</code> specified.</p>
43710      * </ul></div></ul></div>
43711      */
43712
43713     /**
43714      * @cfg {String/Number} activeItem
43715      * A string component id or the numeric index of the component that should be initially activated within the
43716      * container's layout on render.  For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
43717      * item in the container's collection).  activeItem only applies to layout styles that can display
43718      * items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}).
43719      */
43720     /**
43721      * @cfg {Object/Object[]} items
43722      * <p>A single item, or an array of child Components to be added to this container</p>
43723      * <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into
43724      * its encapsulating element and performs no sizing or positioning upon them.</b><p>
43725      * <p>Example:</p>
43726      * <pre><code>
43727 // specifying a single item
43728 items: {...},
43729 layout: 'fit',    // The single items is sized to fit
43730
43731 // specifying multiple items
43732 items: [{...}, {...}],
43733 layout: 'hbox', // The items are arranged horizontally
43734        </code></pre>
43735      * <p>Each item may be:</p>
43736      * <ul>
43737      * <li>A {@link Ext.Component Component}</li>
43738      * <li>A Component configuration object</li>
43739      * </ul>
43740      * <p>If a configuration object is specified, the actual type of Component to be
43741      * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p>
43742      * <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p>
43743      * <p>If an {@link Ext.Component#xtype xtype} is not explicitly
43744      * specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p>
43745      * <p><b>Notes</b>:</p>
43746      * <p>Ext uses lazy rendering. Child Components will only be rendered
43747      * should it become necessary. Items are automatically laid out when they are first
43748      * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p>
43749      * <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or
43750      * <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p>
43751      */
43752     /**
43753      * @cfg {Object/Function} defaults
43754      * This option is a means of applying default settings to all added items whether added through the {@link #items}
43755      * config or via the {@link #add} or {@link #insert} methods.
43756      *
43757      * Defaults are applied to both config objects and instantiated components conditionally so as not to override
43758      * existing properties in the item (see {@link Ext#applyIf}).
43759      *
43760      * If the defaults option is specified as a function, then the function will be called using this Container as the
43761      * scope (`this` reference) and passing the added item as the first parameter. Any resulting object
43762      * from that call is then applied to the item as default properties.
43763      *
43764      * For example, to automatically apply padding to the body of each of a set of
43765      * contained {@link Ext.panel.Panel} items, you could pass: `defaults: {bodyStyle:'padding:15px'}`.
43766      *
43767      * Usage:
43768      *
43769      *     defaults: { // defaults are applied to items, not the container
43770      *         autoScroll: true
43771      *     },
43772      *     items: [
43773      *         // default will not be applied here, panel1 will be autoScroll: false
43774      *         {
43775      *             xtype: 'panel',
43776      *             id: 'panel1',
43777      *             autoScroll: false
43778      *         },
43779      *         // this component will have autoScroll: true
43780      *         new Ext.panel.Panel({
43781      *             id: 'panel2'
43782      *         })
43783      *     ]
43784      */
43785
43786     /** @cfg {Boolean} suspendLayout
43787      * If true, suspend calls to doLayout.  Useful when batching multiple adds to a container and not passing them
43788      * as multiple arguments or an array.
43789      */
43790     suspendLayout : false,
43791
43792     /** @cfg {Boolean} autoDestroy
43793      * If true the container will automatically destroy any contained component that is removed from it, else
43794      * destruction must be handled manually.
43795      * Defaults to true.
43796      */
43797     autoDestroy : true,
43798
43799      /** @cfg {String} defaultType
43800       * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
43801       * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
43802       * <p>Defaults to <code>'panel'</code>.</p>
43803       */
43804     defaultType: 'panel',
43805
43806     isContainer : true,
43807
43808     /**
43809      * The number of container layout calls made on this object.
43810      * @property layoutCounter
43811      * @type {Number}
43812      * @private
43813      */
43814     layoutCounter : 0,
43815
43816     baseCls: Ext.baseCSSPrefix + 'container',
43817
43818     /**
43819      * @cfg {String[]} bubbleEvents
43820      * <p>An array of events that, when fired, should be bubbled to any parent container.
43821      * See {@link Ext.util.Observable#enableBubble}.
43822      * Defaults to <code>['add', 'remove']</code>.
43823      */
43824     bubbleEvents: ['add', 'remove'],
43825
43826     // @private
43827     initComponent : function(){
43828         var me = this;
43829         me.addEvents(
43830             /**
43831              * @event afterlayout
43832              * Fires when the components in this container are arranged by the associated layout manager.
43833              * @param {Ext.container.Container} this
43834              * @param {Ext.layout.container.Container} layout The ContainerLayout implementation for this container
43835              */
43836             'afterlayout',
43837             /**
43838              * @event beforeadd
43839              * Fires before any {@link Ext.Component} is added or inserted into the container.
43840              * A handler can return false to cancel the add.
43841              * @param {Ext.container.Container} this
43842              * @param {Ext.Component} component The component being added
43843              * @param {Number} index The index at which the component will be added to the container's items collection
43844              */
43845             'beforeadd',
43846             /**
43847              * @event beforeremove
43848              * Fires before any {@link Ext.Component} is removed from the container.  A handler can return
43849              * false to cancel the remove.
43850              * @param {Ext.container.Container} this
43851              * @param {Ext.Component} component The component being removed
43852              */
43853             'beforeremove',
43854             /**
43855              * @event add
43856              * @bubbles
43857              * Fires after any {@link Ext.Component} is added or inserted into the container.
43858              * @param {Ext.container.Container} this
43859              * @param {Ext.Component} component The component that was added
43860              * @param {Number} index The index at which the component was added to the container's items collection
43861              */
43862             'add',
43863             /**
43864              * @event remove
43865              * @bubbles
43866              * Fires after any {@link Ext.Component} is removed from the container.
43867              * @param {Ext.container.Container} this
43868              * @param {Ext.Component} component The component that was removed
43869              */
43870             'remove'
43871         );
43872
43873         // layoutOnShow stack
43874         me.layoutOnShow = Ext.create('Ext.util.MixedCollection');
43875         me.callParent();
43876         me.initItems();
43877     },
43878
43879     // @private
43880     initItems : function() {
43881         var me = this,
43882             items = me.items;
43883
43884         /**
43885          * The MixedCollection containing all the child items of this container.
43886          * @property items
43887          * @type Ext.util.MixedCollection
43888          */
43889         me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
43890
43891         if (items) {
43892             if (!Ext.isArray(items)) {
43893                 items = [items];
43894             }
43895
43896             me.add(items);
43897         }
43898     },
43899
43900     // @private
43901     afterRender : function() {
43902         this.getLayout();
43903         this.callParent();
43904     },
43905
43906     renderChildren: function () {
43907         var me = this,
43908             layout = me.getLayout();
43909
43910         me.callParent();
43911         // this component's elements exist, so now create the child components' elements
43912
43913         if (layout) {
43914             me.suspendLayout = true;
43915             layout.renderChildren();
43916             delete me.suspendLayout;
43917         }
43918     },
43919
43920     // @private
43921     setLayout : function(layout) {
43922         var currentLayout = this.layout;
43923
43924         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
43925             currentLayout.setOwner(null);
43926         }
43927
43928         this.layout = layout;
43929         layout.setOwner(this);
43930     },
43931
43932     /**
43933      * Returns the {@link Ext.layout.container.AbstractContainer layout} instance currently associated with this Container.
43934      * If a layout has not been instantiated yet, that is done first
43935      * @return {Ext.layout.container.AbstractContainer} The layout
43936      */
43937     getLayout : function() {
43938         var me = this;
43939         if (!me.layout || !me.layout.isLayout) {
43940             me.setLayout(Ext.layout.Layout.create(me.layout, 'autocontainer'));
43941         }
43942
43943         return me.layout;
43944     },
43945
43946     /**
43947      * Manually force this container's layout to be recalculated. The framework uses this internally to refresh layouts
43948      * form most cases.
43949      * @return {Ext.container.Container} this
43950      */
43951     doLayout : function() {
43952         var me = this,
43953             layout = me.getLayout();
43954
43955         if (me.rendered && layout && !me.suspendLayout) {
43956             // If either dimension is being auto-set, then it requires a ComponentLayout to be run.
43957             if (!me.isFixedWidth() || !me.isFixedHeight()) {
43958                 // Only run the ComponentLayout if it is not already in progress
43959                 if (me.componentLayout.layoutBusy !== true) {
43960                     me.doComponentLayout();
43961                     if (me.componentLayout.layoutCancelled === true) {
43962                         layout.layout();
43963                     }
43964                 }
43965             }
43966             // Both dimensions set, either by configuration, or by an owning layout, run a ContainerLayout
43967             else {
43968                 // Only run the ContainerLayout if it is not already in progress
43969                 if (layout.layoutBusy !== true) {
43970                     layout.layout();
43971                 }
43972             }
43973         }
43974
43975         return me;
43976     },
43977
43978     // @private
43979     afterLayout : function(layout) {
43980         ++this.layoutCounter;
43981         this.fireEvent('afterlayout', this, layout);
43982     },
43983
43984     // @private
43985     prepareItems : function(items, applyDefaults) {
43986         if (!Ext.isArray(items)) {
43987             items = [items];
43988         }
43989
43990         // Make sure defaults are applied and item is initialized
43991         var i = 0,
43992             len = items.length,
43993             item;
43994
43995         for (; i < len; i++) {
43996             item = items[i];
43997             if (applyDefaults) {
43998                 item = this.applyDefaults(item);
43999             }
44000             items[i] = this.lookupComponent(item);
44001         }
44002         return items;
44003     },
44004
44005     // @private
44006     applyDefaults : function(config) {
44007         var defaults = this.defaults;
44008
44009         if (defaults) {
44010             if (Ext.isFunction(defaults)) {
44011                 defaults = defaults.call(this, config);
44012             }
44013
44014             if (Ext.isString(config)) {
44015                 config = Ext.ComponentManager.get(config);
44016             }
44017             Ext.applyIf(config, defaults);
44018         }
44019
44020         return config;
44021     },
44022
44023     // @private
44024     lookupComponent : function(comp) {
44025         return Ext.isString(comp) ? Ext.ComponentManager.get(comp) : this.createComponent(comp);
44026     },
44027
44028     // @private
44029     createComponent : function(config, defaultType) {
44030         // // add in ownerCt at creation time but then immediately
44031         // // remove so that onBeforeAdd can handle it
44032         // var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType);
44033         //
44034         // delete component.initialConfig.ownerCt;
44035         // delete component.ownerCt;
44036
44037         return Ext.ComponentManager.create(config, defaultType || this.defaultType);
44038     },
44039
44040     // @private - used as the key lookup function for the items collection
44041     getComponentId : function(comp) {
44042         return comp.getItemId();
44043     },
44044
44045     /**
44046
44047 Adds {@link Ext.Component Component}(s) to this Container.
44048
44049 ##Description:##
44050
44051 - Fires the {@link #beforeadd} event before adding.
44052 - The Container's {@link #defaults default config values} will be applied
44053   accordingly (see `{@link #defaults}` for details).
44054 - Fires the `{@link #add}` event after the component has been added.
44055
44056 ##Notes:##
44057
44058 If the Container is __already rendered__ when `add`
44059 is called, it will render the newly added Component into its content area.
44060
44061 __**If**__ the Container was configured with a size-managing {@link #layout} manager, the Container
44062 will recalculate its internal layout at this time too.
44063
44064 Note that the default layout manager simply renders child Components sequentially into the content area and thereafter performs no sizing.
44065
44066 If adding multiple new child Components, pass them as an array to the `add` method, so that only one layout recalculation is performed.
44067
44068     tb = new {@link Ext.toolbar.Toolbar}({
44069         renderTo: document.body
44070     });  // toolbar is rendered
44071     tb.add([{text:'Button 1'}, {text:'Button 2'}]); // add multiple items. ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
44072
44073 ##Warning:##
44074
44075 Components directly managed by the BorderLayout layout manager
44076 may not be removed or added.  See the Notes for {@link Ext.layout.container.Border BorderLayout}
44077 for more details.
44078
44079      * @param {Ext.Component[]/Ext.Component...} component
44080      * Either one or more Components to add or an Array of Components to add.
44081      * See `{@link #items}` for additional information.
44082      *
44083      * @return {Ext.Component[]/Ext.Component} The Components that were added.
44084      * @markdown
44085      */
44086     add : function() {
44087         var me = this,
44088             args = Array.prototype.slice.call(arguments),
44089             hasMultipleArgs,
44090             items,
44091             results = [],
44092             i,
44093             ln,
44094             item,
44095             index = -1,
44096             cmp;
44097
44098         if (typeof args[0] == 'number') {
44099             index = args.shift();
44100         }
44101
44102         hasMultipleArgs = args.length > 1;
44103         if (hasMultipleArgs || Ext.isArray(args[0])) {
44104
44105             items = hasMultipleArgs ? args : args[0];
44106             // Suspend Layouts while we add multiple items to the container
44107             me.suspendLayout = true;
44108             for (i = 0, ln = items.length; i < ln; i++) {
44109                 item = items[i];
44110
44111                 if (!item) {
44112                     Ext.Error.raise("Trying to add a null item as a child of Container with itemId/id: " + me.getItemId());
44113                 }
44114
44115                 if (index != -1) {
44116                     item = me.add(index + i, item);
44117                 } else {
44118                     item = me.add(item);
44119                 }
44120                 results.push(item);
44121             }
44122             // Resume Layouts now that all items have been added and do a single layout for all the items just added
44123             me.suspendLayout = false;
44124             me.doLayout();
44125             return results;
44126         }
44127
44128         cmp = me.prepareItems(args[0], true)[0];
44129
44130         // Floating Components are not added into the items collection
44131         // But they do get an upward ownerCt link so that they can traverse
44132         // up to their z-index parent.
44133         if (cmp.floating) {
44134             cmp.onAdded(me, index);
44135         } else {
44136             index = (index !== -1) ? index : me.items.length;
44137             if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) {
44138                 me.items.insert(index, cmp);
44139                 cmp.onAdded(me, index);
44140                 me.onAdd(cmp, index);
44141                 me.fireEvent('add', me, cmp, index);
44142             }
44143             me.doLayout();
44144         }
44145         return cmp;
44146     },
44147
44148     onAdd : Ext.emptyFn,
44149     onRemove : Ext.emptyFn,
44150
44151     /**
44152      * Inserts a Component into this Container at a specified index. Fires the
44153      * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
44154      * Component has been inserted.
44155      * @param {Number} index The index at which the Component will be inserted
44156      * into the Container's items collection
44157      * @param {Ext.Component} component The child Component to insert.<br><br>
44158      * Ext uses lazy rendering, and will only render the inserted Component should
44159      * it become necessary.<br><br>
44160      * A Component config object may be passed in order to avoid the overhead of
44161      * constructing a real Component object if lazy rendering might mean that the
44162      * inserted Component will not be rendered immediately. To take advantage of
44163      * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
44164      * property to the registered type of the Component wanted.<br><br>
44165      * For a list of all available xtypes, see {@link Ext.Component}.
44166      * @return {Ext.Component} component The Component (or config object) that was
44167      * inserted with the Container's default config values applied.
44168      */
44169     insert : function(index, comp) {
44170         return this.add(index, comp);
44171     },
44172
44173     /**
44174      * Moves a Component within the Container
44175      * @param {Number} fromIdx The index the Component you wish to move is currently at.
44176      * @param {Number} toIdx The new index for the Component.
44177      * @return {Ext.Component} component The Component (or config object) that was moved.
44178      */
44179     move : function(fromIdx, toIdx) {
44180         var items = this.items,
44181             item;
44182         item = items.removeAt(fromIdx);
44183         if (item === false) {
44184             return false;
44185         }
44186         items.insert(toIdx, item);
44187         this.doLayout();
44188         return item;
44189     },
44190
44191     // @private
44192     onBeforeAdd : function(item) {
44193         var me = this;
44194
44195         if (item.ownerCt) {
44196             item.ownerCt.remove(item, false);
44197         }
44198
44199         if (me.border === false || me.border === 0) {
44200             item.border = (item.border === true);
44201         }
44202     },
44203
44204     /**
44205      * Removes a component from this container.  Fires the {@link #beforeremove} event before removing, then fires
44206      * the {@link #remove} event after the component has been removed.
44207      * @param {Ext.Component/String} component The component reference or id to remove.
44208      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
44209      * Defaults to the value of this Container's {@link #autoDestroy} config.
44210      * @return {Ext.Component} component The Component that was removed.
44211      */
44212     remove : function(comp, autoDestroy) {
44213         var me = this,
44214             c = me.getComponent(comp);
44215             if (Ext.isDefined(Ext.global.console) && !c) {
44216                 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.");
44217             }
44218
44219         if (c && me.fireEvent('beforeremove', me, c) !== false) {
44220             me.doRemove(c, autoDestroy);
44221             me.fireEvent('remove', me, c);
44222         }
44223
44224         return c;
44225     },
44226
44227     // @private
44228     doRemove : function(component, autoDestroy) {
44229         var me = this,
44230             layout = me.layout,
44231             hasLayout = layout && me.rendered;
44232
44233         me.items.remove(component);
44234         component.onRemoved();
44235
44236         if (hasLayout) {
44237             layout.onRemove(component);
44238         }
44239
44240         me.onRemove(component, autoDestroy);
44241
44242         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
44243             component.destroy();
44244         }
44245
44246         if (hasLayout && !autoDestroy) {
44247             layout.afterRemove(component);
44248         }
44249
44250         if (!me.destroying) {
44251             me.doLayout();
44252         }
44253     },
44254
44255     /**
44256      * Removes all components from this container.
44257      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
44258      * Defaults to the value of this Container's {@link #autoDestroy} config.
44259      * @return {Ext.Component[]} Array of the destroyed components
44260      */
44261     removeAll : function(autoDestroy) {
44262         var me = this,
44263             removeItems = me.items.items.slice(),
44264             items = [],
44265             i = 0,
44266             len = removeItems.length,
44267             item;
44268
44269         // Suspend Layouts while we remove multiple items from the container
44270         me.suspendLayout = true;
44271         for (; i < len; i++) {
44272             item = removeItems[i];
44273             me.remove(item, autoDestroy);
44274
44275             if (item.ownerCt !== me) {
44276                 items.push(item);
44277             }
44278         }
44279
44280         // Resume Layouts now that all items have been removed and do a single layout (if we removed anything!)
44281         me.suspendLayout = false;
44282         if (len) {
44283             me.doLayout();
44284         }
44285         return items;
44286     },
44287
44288     // Used by ComponentQuery to retrieve all of the items
44289     // which can potentially be considered a child of this Container.
44290     // This should be overriden by components which have child items
44291     // that are not contained in items. For example dockedItems, menu, etc
44292     // IMPORTANT note for maintainers:
44293     //  Items are returned in tree traversal order. Each item is appended to the result array
44294     //  followed by the results of that child's getRefItems call.
44295     //  Floating child items are appended after internal child items.
44296     getRefItems : function(deep) {
44297         var me = this,
44298             items = me.items.items,
44299             len = items.length,
44300             i = 0,
44301             item,
44302             result = [];
44303
44304         for (; i < len; i++) {
44305             item = items[i];
44306             result.push(item);
44307             if (deep && item.getRefItems) {
44308                 result.push.apply(result, item.getRefItems(true));
44309             }
44310         }
44311
44312         // Append floating items to the list.
44313         // These will only be present after they are rendered.
44314         if (me.floatingItems && me.floatingItems.accessList) {
44315             result.push.apply(result, me.floatingItems.accessList);
44316         }
44317
44318         return result;
44319     },
44320
44321     /**
44322      * Cascades down the component/container heirarchy from this component (passed in the first call), calling the specified function with
44323      * each component. The scope (<code>this</code> reference) of the
44324      * function call will be the scope provided or the current component. The arguments to the function
44325      * will be the args provided or the current component. If the function returns false at any point,
44326      * the cascade is stopped on that branch.
44327      * @param {Function} fn The function to call
44328      * @param {Object} [scope] The scope of the function (defaults to current component)
44329      * @param {Array} [args] The args to call the function with. The current component always passed as the last argument.
44330      * @return {Ext.Container} this
44331      */
44332     cascade : function(fn, scope, origArgs){
44333         var me = this,
44334             cs = me.items ? me.items.items : [],
44335             len = cs.length,
44336             i = 0,
44337             c,
44338             args = origArgs ? origArgs.concat(me) : [me],
44339             componentIndex = args.length - 1;
44340
44341         if (fn.apply(scope || me, args) !== false) {
44342             for(; i < len; i++){
44343                 c = cs[i];
44344                 if (c.cascade) {
44345                     c.cascade(fn, scope, origArgs);
44346                 } else {
44347                     args[componentIndex] = c;
44348                     fn.apply(scope || cs, args);
44349                 }
44350             }
44351         }
44352         return this;
44353     },
44354
44355     /**
44356      * Examines this container's <code>{@link #items}</code> <b>property</b>
44357      * and gets a direct child component of this container.
44358      * @param {String/Number} comp This parameter may be any of the following:
44359      * <div><ul class="mdetail-params">
44360      * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
44361      * or <code>{@link Ext.Component#id id}</code> of the child component </li>
44362      * <li>a <b><code>Number</code></b> : representing the position of the child component
44363      * within the <code>{@link #items}</code> <b>property</b></li>
44364      * </ul></div>
44365      * <p>For additional information see {@link Ext.util.MixedCollection#get}.
44366      * @return Ext.Component The component (if found).
44367      */
44368     getComponent : function(comp) {
44369         if (Ext.isObject(comp)) {
44370             comp = comp.getItemId();
44371         }
44372
44373         return this.items.get(comp);
44374     },
44375
44376     /**
44377      * Retrieves all descendant components which match the passed selector.
44378      * Executes an Ext.ComponentQuery.query using this container as its root.
44379      * @param {String} selector (optional) Selector complying to an Ext.ComponentQuery selector.
44380      * If no selector is specified all items will be returned.
44381      * @return {Ext.Component[]} Components which matched the selector
44382      */
44383     query : function(selector) {
44384         selector = selector || '*';
44385         return Ext.ComponentQuery.query(selector, this);
44386     },
44387
44388     /**
44389      * Retrieves the first direct child of this container which matches the passed selector.
44390      * The passed in selector must comply with an Ext.ComponentQuery selector.
44391      * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
44392      * specified, the first child will be returned.
44393      * @return Ext.Component
44394      */
44395     child : function(selector) {
44396         selector = selector || '';
44397         return this.query('> ' + selector)[0] || null;
44398     },
44399
44400     /**
44401      * Retrieves the first descendant of this container which matches the passed selector.
44402      * The passed in selector must comply with an Ext.ComponentQuery selector.
44403      * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
44404      * specified, the first child will be returned.
44405      * @return Ext.Component
44406      */
44407     down : function(selector) {
44408         return this.query(selector)[0] || null;
44409     },
44410
44411     // inherit docs
44412     show : function() {
44413         this.callParent(arguments);
44414         this.performDeferredLayouts();
44415         return this;
44416     },
44417
44418     // Lay out any descendant containers who queued a layout operation during the time this was hidden
44419     // This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops.
44420     performDeferredLayouts: function() {
44421         var layoutCollection = this.layoutOnShow,
44422             ln = layoutCollection.getCount(),
44423             i = 0,
44424             needsLayout,
44425             item;
44426
44427         for (; i < ln; i++) {
44428             item = layoutCollection.get(i);
44429             needsLayout = item.needsLayout;
44430
44431             if (Ext.isObject(needsLayout)) {
44432                 item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
44433             }
44434         }
44435         layoutCollection.clear();
44436     },
44437
44438     //@private
44439     // Enable all immediate children that was previously disabled
44440     onEnable: function() {
44441         Ext.Array.each(this.query('[isFormField]'), function(item) {
44442             if (item.resetDisable) {
44443                 item.enable();
44444                 delete item.resetDisable;
44445             }
44446         });
44447         this.callParent();
44448     },
44449
44450     // @private
44451     // Disable all immediate children that was previously disabled
44452     onDisable: function() {
44453         Ext.Array.each(this.query('[isFormField]'), function(item) {
44454             if (item.resetDisable !== false && !item.disabled) {
44455                 item.disable();
44456                 item.resetDisable = true;
44457             }
44458         });
44459         this.callParent();
44460     },
44461
44462     /**
44463      * Occurs before componentLayout is run. Returning false from this method will prevent the containerLayout
44464      * from being executed.
44465      */
44466     beforeLayout: function() {
44467         return true;
44468     },
44469
44470     // @private
44471     beforeDestroy : function() {
44472         var me = this,
44473             items = me.items,
44474             c;
44475
44476         if (items) {
44477             while ((c = items.first())) {
44478                 me.doRemove(c, true);
44479             }
44480         }
44481
44482         Ext.destroy(
44483             me.layout
44484         );
44485         me.callParent();
44486     }
44487 });
44488
44489 /**
44490  * Base class for any Ext.Component that may contain other Components. Containers handle the basic behavior of
44491  * containing items, namely adding, inserting and removing items.
44492  *
44493  * The most commonly used Container classes are Ext.panel.Panel, Ext.window.Window and
44494  * Ext.tab.Panel. If you do not need the capabilities offered by the aforementioned classes you can create a
44495  * lightweight Container to be encapsulated by an HTML element to your specifications by using the
44496  * {@link Ext.Component#autoEl autoEl} config option.
44497  *
44498  * The code below illustrates how to explicitly create a Container:
44499  *
44500  *     @example
44501  *     // Explicitly create a Container
44502  *     Ext.create('Ext.container.Container', {
44503  *         layout: {
44504  *             type: 'hbox'
44505  *         },
44506  *         width: 400,
44507  *         renderTo: Ext.getBody(),
44508  *         border: 1,
44509  *         style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
44510  *         defaults: {
44511  *             labelWidth: 80,
44512  *             // implicitly create Container by specifying xtype
44513  *             xtype: 'datefield',
44514  *             flex: 1,
44515  *             style: {
44516  *                 padding: '10px'
44517  *             }
44518  *         },
44519  *         items: [{
44520  *             xtype: 'datefield',
44521  *             name: 'startDate',
44522  *             fieldLabel: 'Start date'
44523  *         },{
44524  *             xtype: 'datefield',
44525  *             name: 'endDate',
44526  *             fieldLabel: 'End date'
44527  *         }]
44528  *     });
44529  *
44530  * ## Layout
44531  *
44532  * Container classes delegate the rendering of child Components to a layout manager class which must be configured into
44533  * the Container using the `{@link #layout}` configuration property.
44534  *
44535  * When either specifying child `{@link #items}` of a Container, or dynamically {@link #add adding} Components to a
44536  * Container, remember to consider how you wish the Container to arrange those child elements, and whether those child
44537  * elements need to be sized using one of Ext's built-in `{@link #layout}` schemes. By default, Containers use the
44538  * {@link Ext.layout.container.Auto Auto} scheme which only renders child components, appending them one after the other
44539  * inside the Container, and **does not apply any sizing** at all.
44540  *
44541  * A common mistake is when a developer neglects to specify a `{@link #layout}` (e.g. widgets like GridPanels or
44542  * TreePanels are added to Containers for which no `{@link #layout}` has been specified). If a Container is left to
44543  * use the default {@link Ext.layout.container.Auto Auto} scheme, none of its child components will be resized, or changed in
44544  * any way when the Container is resized.
44545  *
44546  * Certain layout managers allow dynamic addition of child components. Those that do include
44547  * Ext.layout.container.Card, Ext.layout.container.Anchor, Ext.layout.container.VBox,
44548  * Ext.layout.container.HBox, and Ext.layout.container.Table. For example:
44549  *
44550  *     //  Create the GridPanel.
44551  *     var myNewGrid = new Ext.grid.Panel({
44552  *         store: myStore,
44553  *         headers: myHeaders,
44554  *         title: 'Results', // the title becomes the title of the tab
44555  *     });
44556  *
44557  *     myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
44558  *     myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
44559  *
44560  * The example above adds a newly created GridPanel to a TabPanel. Note that a TabPanel uses {@link
44561  * Ext.layout.container.Card} as its layout manager which means all its child items are sized to {@link
44562  * Ext.layout.container.Fit fit} exactly into its client area.
44563  *
44564  * **_Overnesting is a common problem_**. An example of overnesting occurs when a GridPanel is added to a TabPanel by
44565  * wrapping the GridPanel _inside_ a wrapping Panel (that has no `{@link #layout}` specified) and then add that
44566  * wrapping Panel to the TabPanel. The point to realize is that a GridPanel **is** a Component which can be added
44567  * directly to a Container. If the wrapping Panel has no `{@link #layout}` configuration, then the overnested
44568  * GridPanel will not be sized as expected.
44569  *
44570  * ## Adding via remote configuration
44571  *
44572  * A server side script can be used to add Components which are generated dynamically on the server. An example of
44573  * adding a GridPanel to a TabPanel where the GridPanel is generated by the server based on certain parameters:
44574  *
44575  *     // execute an Ajax request to invoke server side script:
44576  *     Ext.Ajax.request({
44577  *         url: 'gen-invoice-grid.php',
44578  *         // send additional parameters to instruct server script
44579  *         params: {
44580  *             startDate: Ext.getCmp('start-date').getValue(),
44581  *             endDate: Ext.getCmp('end-date').getValue()
44582  *         },
44583  *         // process the response object to add it to the TabPanel:
44584  *         success: function(xhr) {
44585  *             var newComponent = eval(xhr.responseText); // see discussion below
44586  *             myTabPanel.add(newComponent); // add the component to the TabPanel
44587  *             myTabPanel.setActiveTab(newComponent);
44588  *         },
44589  *         failure: function() {
44590  *             Ext.Msg.alert("Grid create failed", "Server communication failure");
44591  *         }
44592  *     });
44593  *
44594  * The server script needs to return a JSON representation of a configuration object, which, when decoded will return a
44595  * config object with an {@link Ext.Component#xtype xtype}. The server might return the following JSON:
44596  *
44597  *     {
44598  *         "xtype": 'grid',
44599  *         "title": 'Invoice Report',
44600  *         "store": {
44601  *             "model": 'Invoice',
44602  *             "proxy": {
44603  *                 "type": 'ajax',
44604  *                 "url": 'get-invoice-data.php',
44605  *                 "reader": {
44606  *                     "type": 'json'
44607  *                     "record": 'transaction',
44608  *                     "idProperty": 'id',
44609  *                     "totalRecords": 'total'
44610  *                 })
44611  *             },
44612  *             "autoLoad": {
44613  *                 "params": {
44614  *                     "startDate": '01/01/2008',
44615  *                     "endDate": '01/31/2008'
44616  *                 }
44617  *             }
44618  *         },
44619  *         "headers": [
44620  *             {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
44621  *             {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
44622  *             {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
44623  *             {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
44624  *         ]
44625  *     }
44626  *
44627  * When the above code fragment is passed through the `eval` function in the success handler of the Ajax request, the
44628  * result will be a config object which, when added to a Container, will cause instantiation of a GridPanel. **Be sure
44629  * that the Container is configured with a layout which sizes and positions the child items to your requirements.**
44630  *
44631  * **Note:** since the code above is _generated_ by a server script, the `autoLoad` params for the Store, the user's
44632  * preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel can all be generated
44633  * into the code since these are all known on the server.
44634  */
44635 Ext.define('Ext.container.Container', {
44636     extend: 'Ext.container.AbstractContainer',
44637     alias: 'widget.container',
44638     alternateClassName: 'Ext.Container',
44639
44640     /**
44641      * Return the immediate child Component in which the passed element is located.
44642      * @param {Ext.Element/HTMLElement/String} el The element to test (or ID of element).
44643      * @return {Ext.Component} The child item which contains the passed element.
44644      */
44645     getChildByElement: function(el) {
44646         var item,
44647             itemEl,
44648             i = 0,
44649             it = this.items.items,
44650             ln = it.length;
44651
44652         el = Ext.getDom(el);
44653         for (; i < ln; i++) {
44654             item = it[i];
44655             itemEl = item.getEl();
44656             if ((itemEl.dom === el) || itemEl.contains(el)) {
44657                 return item;
44658             }
44659         }
44660         return null;
44661     }
44662 });
44663
44664 /**
44665  * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
44666  * the right-justified button container.
44667  *
44668  *     @example
44669  *     Ext.create('Ext.panel.Panel', {
44670  *          title: 'Toolbar Fill Example',
44671  *          width: 300,
44672  *          height: 200,
44673  *          tbar : [
44674  *              'Item 1',
44675  *              { xtype: 'tbfill' },
44676  *              'Item 2'
44677  *          ],
44678  *          renderTo: Ext.getBody()
44679  *      });
44680  */
44681 Ext.define('Ext.toolbar.Fill', {
44682     extend: 'Ext.Component',
44683     alias: 'widget.tbfill',
44684     alternateClassName: 'Ext.Toolbar.Fill',
44685     isFill : true,
44686     flex: 1
44687 });
44688 /**
44689  * @class Ext.toolbar.Item
44690  * @extends Ext.Component
44691  * The base class that other non-interacting Toolbar Item classes should extend in order to
44692  * get some basic common toolbar item functionality.
44693  */
44694 Ext.define('Ext.toolbar.Item', {
44695     extend: 'Ext.Component',
44696     alias: 'widget.tbitem',
44697     alternateClassName: 'Ext.Toolbar.Item',
44698     enable:Ext.emptyFn,
44699     disable:Ext.emptyFn,
44700     focus:Ext.emptyFn
44701     /**
44702      * @cfg {String} overflowText Text to be used for the menu if the item is overflowed.
44703      */
44704 });
44705 /**
44706  * @class Ext.toolbar.Separator
44707  * @extends Ext.toolbar.Item
44708  * A simple class that adds a vertical separator bar between toolbar items (css class: 'x-toolbar-separator').
44709  *
44710  *     @example
44711  *     Ext.create('Ext.panel.Panel', {
44712  *         title: 'Toolbar Seperator Example',
44713  *         width: 300,
44714  *         height: 200,
44715  *         tbar : [
44716  *             'Item 1',
44717  *             { xtype: 'tbseparator' },
44718  *             'Item 2'
44719  *         ],
44720  *         renderTo: Ext.getBody()
44721  *     });
44722  */
44723 Ext.define('Ext.toolbar.Separator', {
44724     extend: 'Ext.toolbar.Item',
44725     alias: 'widget.tbseparator',
44726     alternateClassName: 'Ext.Toolbar.Separator',
44727     baseCls: Ext.baseCSSPrefix + 'toolbar-separator',
44728     focusable: false
44729 });
44730 /**
44731  * @class Ext.menu.Manager
44732  * Provides a common registry of all menus on a page.
44733  * @singleton
44734  */
44735 Ext.define('Ext.menu.Manager', {
44736     singleton: true,
44737     requires: [
44738         'Ext.util.MixedCollection',
44739         'Ext.util.KeyMap'
44740     ],
44741     alternateClassName: 'Ext.menu.MenuMgr',
44742
44743     uses: ['Ext.menu.Menu'],
44744
44745     menus: {},
44746     groups: {},
44747     attached: false,
44748     lastShow: new Date(),
44749
44750     init: function() {
44751         var me = this;
44752         
44753         me.active = Ext.create('Ext.util.MixedCollection');
44754         Ext.getDoc().addKeyListener(27, function() {
44755             if (me.active.length > 0) {
44756                 me.hideAll();
44757             }
44758         }, me);
44759     },
44760
44761     /**
44762      * Hides all menus that are currently visible
44763      * @return {Boolean} success True if any active menus were hidden.
44764      */
44765     hideAll: function() {
44766         var active = this.active,
44767             c;
44768         if (active && active.length > 0) {
44769             c = active.clone();
44770             c.each(function(m) {
44771                 m.hide();
44772             });
44773             return true;
44774         }
44775         return false;
44776     },
44777
44778     onHide: function(m) {
44779         var me = this,
44780             active = me.active;
44781         active.remove(m);
44782         if (active.length < 1) {
44783             Ext.getDoc().un('mousedown', me.onMouseDown, me);
44784             me.attached = false;
44785         }
44786     },
44787
44788     onShow: function(m) {
44789         var me = this,
44790             active   = me.active,
44791             last     = active.last(),
44792             attached = me.attached,
44793             menuEl   = m.getEl(),
44794             zIndex;
44795
44796         me.lastShow = new Date();
44797         active.add(m);
44798         if (!attached) {
44799             Ext.getDoc().on('mousedown', me.onMouseDown, me);
44800             me.attached = true;
44801         }
44802         m.toFront();
44803     },
44804
44805     onBeforeHide: function(m) {
44806         if (m.activeChild) {
44807             m.activeChild.hide();
44808         }
44809         if (m.autoHideTimer) {
44810             clearTimeout(m.autoHideTimer);
44811             delete m.autoHideTimer;
44812         }
44813     },
44814
44815     onBeforeShow: function(m) {
44816         var active = this.active,
44817             parentMenu = m.parentMenu;
44818             
44819         active.remove(m);
44820         if (!parentMenu && !m.allowOtherMenus) {
44821             this.hideAll();
44822         }
44823         else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
44824             parentMenu.activeChild.hide();
44825         }
44826     },
44827
44828     // private
44829     onMouseDown: function(e) {
44830         var me = this,
44831             active = me.active,
44832             lastShow = me.lastShow,
44833             target = e.target;
44834
44835         if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
44836             me.hideAll();
44837             // in IE, if we mousedown on a focusable element, the focus gets cancelled and the focus event is never
44838             // fired on the element, so we'll focus it here
44839             if (Ext.isIE && Ext.fly(target).focusable()) {
44840                 target.focus();
44841             }
44842         }
44843     },
44844
44845     // private
44846     register: function(menu) {
44847         var me = this;
44848
44849         if (!me.active) {
44850             me.init();
44851         }
44852
44853         if (menu.floating) {
44854             me.menus[menu.id] = menu;
44855             menu.on({
44856                 beforehide: me.onBeforeHide,
44857                 hide: me.onHide,
44858                 beforeshow: me.onBeforeShow,
44859                 show: me.onShow,
44860                 scope: me
44861             });
44862         }
44863     },
44864
44865     /**
44866      * Returns a {@link Ext.menu.Menu} object
44867      * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
44868      * be used to generate and return a new Menu this.
44869      * @return {Ext.menu.Menu} The specified menu, or null if none are found
44870      */
44871     get: function(menu) {
44872         var menus = this.menus;
44873         
44874         if (typeof menu == 'string') { // menu id
44875             if (!menus) {  // not initialized, no menus to return
44876                 return null;
44877             }
44878             return menus[menu];
44879         } else if (menu.isMenu) {  // menu instance
44880             return menu;
44881         } else if (Ext.isArray(menu)) { // array of menu items
44882             return Ext.create('Ext.menu.Menu', {items:menu});
44883         } else { // otherwise, must be a config
44884             return Ext.ComponentManager.create(menu, 'menu');
44885         }
44886     },
44887
44888     // private
44889     unregister: function(menu) {
44890         var me = this,
44891             menus = me.menus,
44892             active = me.active;
44893
44894         delete menus[menu.id];
44895         active.remove(menu);
44896         menu.un({
44897             beforehide: me.onBeforeHide,
44898             hide: me.onHide,
44899             beforeshow: me.onBeforeShow,
44900             show: me.onShow,
44901             scope: me
44902         });
44903     },
44904
44905     // private
44906     registerCheckable: function(menuItem) {
44907         var groups  = this.groups,
44908             groupId = menuItem.group;
44909
44910         if (groupId) {
44911             if (!groups[groupId]) {
44912                 groups[groupId] = [];
44913             }
44914
44915             groups[groupId].push(menuItem);
44916         }
44917     },
44918
44919     // private
44920     unregisterCheckable: function(menuItem) {
44921         var groups  = this.groups,
44922             groupId = menuItem.group;
44923
44924         if (groupId) {
44925             Ext.Array.remove(groups[groupId], menuItem);
44926         }
44927     },
44928
44929     onCheckChange: function(menuItem, state) {
44930         var groups  = this.groups,
44931             groupId = menuItem.group,
44932             i       = 0,
44933             group, ln, curr;
44934
44935         if (groupId && state) {
44936             group = groups[groupId];
44937             ln = group.length;
44938             for (; i < ln; i++) {
44939                 curr = group[i];
44940                 if (curr != menuItem) {
44941                     curr.setChecked(false);
44942                 }
44943             }
44944         }
44945     }
44946 });
44947 /**
44948  * Component layout for buttons
44949  * @class Ext.layout.component.Button
44950  * @extends Ext.layout.component.Component
44951  * @private
44952  */
44953 Ext.define('Ext.layout.component.Button', {
44954
44955     /* Begin Definitions */
44956
44957     alias: ['layout.button'],
44958
44959     extend: 'Ext.layout.component.Component',
44960
44961     /* End Definitions */
44962
44963     type: 'button',
44964
44965     cellClsRE: /-btn-(tl|br)\b/,
44966     htmlRE: /<.*>/,
44967
44968     beforeLayout: function() {
44969         return this.callParent(arguments) || this.lastText !== this.owner.text;
44970     },
44971
44972     /**
44973      * Set the dimensions of the inner &lt;button&gt; element to match the
44974      * component dimensions.
44975      */
44976     onLayout: function(width, height) {
44977         var me = this,
44978             isNum = Ext.isNumber,
44979             owner = me.owner,
44980             ownerEl = owner.el,
44981             btnEl = owner.btnEl,
44982             btnInnerEl = owner.btnInnerEl,
44983             btnIconEl = owner.btnIconEl,
44984             sizeIconEl = (owner.icon || owner.iconCls) && (owner.iconAlign == "top" || owner.iconAlign == "bottom"),
44985             minWidth = owner.minWidth,
44986             maxWidth = owner.maxWidth,
44987             ownerWidth, btnFrameWidth, metrics;
44988
44989         me.getTargetInfo();
44990         me.callParent(arguments);
44991
44992         btnInnerEl.unclip();
44993         me.setTargetSize(width, height);
44994
44995         if (!isNum(width)) {
44996             // In IE7 strict mode button elements with width:auto get strange extra side margins within
44997             // the wrapping table cell, but they go away if the width is explicitly set. So we measure
44998             // the size of the text and set the width to match.
44999             if (owner.text && (Ext.isIE6 || Ext.isIE7) && Ext.isStrict && btnEl && btnEl.getWidth() > 20) {
45000                 btnFrameWidth = me.btnFrameWidth;
45001                 metrics = Ext.util.TextMetrics.measure(btnInnerEl, owner.text);
45002                 ownerEl.setWidth(metrics.width + btnFrameWidth + me.adjWidth);
45003                 btnEl.setWidth(metrics.width + btnFrameWidth);
45004                 btnInnerEl.setWidth(metrics.width + btnFrameWidth);
45005
45006                 if (sizeIconEl) {
45007                     btnIconEl.setWidth(metrics.width + btnFrameWidth);
45008                 }
45009             } else {
45010                 // Remove any previous fixed widths
45011                 ownerEl.setWidth(null);
45012                 btnEl.setWidth(null);
45013                 btnInnerEl.setWidth(null);
45014                 btnIconEl.setWidth(null);
45015             }
45016
45017             // Handle maxWidth/minWidth config
45018             if (minWidth || maxWidth) {
45019                 ownerWidth = ownerEl.getWidth();
45020                 if (minWidth && (ownerWidth < minWidth)) {
45021                     me.setTargetSize(minWidth, height);
45022                 }
45023                 else if (maxWidth && (ownerWidth > maxWidth)) {
45024                     btnInnerEl.clip();
45025                     me.setTargetSize(maxWidth, height);
45026                 }
45027             }
45028         }
45029
45030         this.lastText = owner.text;
45031     },
45032
45033     setTargetSize: function(width, height) {
45034         var me = this,
45035             owner = me.owner,
45036             isNum = Ext.isNumber,
45037             btnInnerEl = owner.btnInnerEl,
45038             btnWidth = (isNum(width) ? width - me.adjWidth : width),
45039             btnHeight = (isNum(height) ? height - me.adjHeight : height),
45040             btnFrameHeight = me.btnFrameHeight,
45041             text = owner.getText(),
45042             textHeight;
45043
45044         me.callParent(arguments);
45045         me.setElementSize(owner.btnEl, btnWidth, btnHeight);
45046         me.setElementSize(btnInnerEl, btnWidth, btnHeight);
45047         if (btnHeight >= 0) {
45048             btnInnerEl.setStyle('line-height', btnHeight - btnFrameHeight + 'px');
45049         }
45050
45051         // Button text may contain markup that would force it to wrap to more than one line (e.g. 'Button<br>Label').
45052         // When this happens, we cannot use the line-height set above for vertical centering; we instead reset the
45053         // line-height to normal, measure the rendered text height, and add padding-top to center the text block
45054         // vertically within the button's height. This is more expensive than the basic line-height approach so
45055         // we only do it if the text contains markup.
45056         if (text && this.htmlRE.test(text)) {
45057             btnInnerEl.setStyle('line-height', 'normal');
45058             textHeight = Ext.util.TextMetrics.measure(btnInnerEl, text).height;
45059             btnInnerEl.setStyle('padding-top', me.btnFrameTop + Math.max(btnInnerEl.getHeight() - btnFrameHeight - textHeight, 0) / 2 + 'px');
45060             me.setElementSize(btnInnerEl, btnWidth, btnHeight);
45061         }
45062     },
45063
45064     getTargetInfo: function() {
45065         var me = this,
45066             owner = me.owner,
45067             ownerEl = owner.el,
45068             frameSize = me.frameSize,
45069             frameBody = owner.frameBody,
45070             btnWrap = owner.btnWrap,
45071             innerEl = owner.btnInnerEl;
45072
45073         if (!('adjWidth' in me)) {
45074             Ext.apply(me, {
45075                 // Width adjustment must take into account the arrow area. The btnWrap is the <em> which has padding to accommodate the arrow.
45076                 adjWidth: frameSize.left + frameSize.right + ownerEl.getBorderWidth('lr') + ownerEl.getPadding('lr') +
45077                           btnWrap.getPadding('lr') + (frameBody ? frameBody.getFrameWidth('lr') : 0),
45078                 adjHeight: frameSize.top + frameSize.bottom + ownerEl.getBorderWidth('tb') + ownerEl.getPadding('tb') +
45079                            btnWrap.getPadding('tb') + (frameBody ? frameBody.getFrameWidth('tb') : 0),
45080                 btnFrameWidth: innerEl.getFrameWidth('lr'),
45081                 btnFrameHeight: innerEl.getFrameWidth('tb'),
45082                 btnFrameTop: innerEl.getFrameWidth('t')
45083             });
45084         }
45085
45086         return me.callParent();
45087     }
45088 });
45089 /**
45090  * @docauthor Robert Dougan <rob@sencha.com>
45091  *
45092  * Create simple buttons with this component. Customisations include {@link #iconAlign aligned}
45093  * {@link #iconCls icons}, {@link #menu dropdown menus}, {@link #tooltip tooltips}
45094  * and {@link #scale sizing options}. Specify a {@link #handler handler} to run code when
45095  * a user clicks the button, or use {@link #listeners listeners} for other events such as
45096  * {@link #mouseover mouseover}. Example usage:
45097  *
45098  *     @example
45099  *     Ext.create('Ext.Button', {
45100  *         text: 'Click me',
45101  *         renderTo: Ext.getBody(),
45102  *         handler: function() {
45103  *             alert('You clicked the button!')
45104  *         }
45105  *     });
45106  *
45107  * The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler}
45108  * method.  Example usage:
45109  *
45110  *     @example
45111  *     Ext.create('Ext.Button', {
45112  *         text    : 'Dynamic Handler Button',
45113  *         renderTo: Ext.getBody(),
45114  *         handler : function() {
45115  *             // this button will spit out a different number every time you click it.
45116  *             // so firstly we must check if that number is already set:
45117  *             if (this.clickCount) {
45118  *                 // looks like the property is already set, so lets just add 1 to that number and alert the user
45119  *                 this.clickCount++;
45120  *                 alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
45121  *             } else {
45122  *                 // if the clickCount property is not set, we will set it and alert the user
45123  *                 this.clickCount = 1;
45124  *                 alert('You just clicked the button for the first time!\n\nTry pressing it again..');
45125  *             }
45126  *         }
45127  *     });
45128  *
45129  * A button within a container:
45130  *
45131  *     @example
45132  *     Ext.create('Ext.Container', {
45133  *         renderTo: Ext.getBody(),
45134  *         items   : [
45135  *             {
45136  *                 xtype: 'button',
45137  *                 text : 'My Button'
45138  *             }
45139  *         ]
45140  *     });
45141  *
45142  * A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
45143  *
45144  * - `'small'`
45145  * - `'medium'`
45146  * - `'large'`
45147  *
45148  * Example usage:
45149  *
45150  *     @example
45151  *     Ext.create('Ext.Button', {
45152  *         renderTo: document.body,
45153  *         text    : 'Click me',
45154  *         scale   : 'large'
45155  *     });
45156  *
45157  * Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
45158  * Example usage:
45159  *
45160  *     @example
45161  *     Ext.create('Ext.Button', {
45162  *         renderTo: Ext.getBody(),
45163  *         text: 'Click Me',
45164  *         enableToggle: true
45165  *     });
45166  *
45167  * You can assign a menu to a button by using the {@link #menu} configuration. This standard configuration
45168  * can either be a reference to a {@link Ext.menu.Menu menu} object, a {@link Ext.menu.Menu menu} id or a
45169  * {@link Ext.menu.Menu menu} config blob. When assigning a menu to a button, an arrow is automatically
45170  * added to the button.  You can change the alignment of the arrow using the {@link #arrowAlign} configuration
45171  * on button.  Example usage:
45172  *
45173  *     @example
45174  *     Ext.create('Ext.Button', {
45175  *         text      : 'Menu button',
45176  *         renderTo  : Ext.getBody(),
45177  *         arrowAlign: 'bottom',
45178  *         menu      : [
45179  *             {text: 'Item 1'},
45180  *             {text: 'Item 2'},
45181  *             {text: 'Item 3'},
45182  *             {text: 'Item 4'}
45183  *         ]
45184  *     });
45185  *
45186  * Using listeners, you can easily listen to events fired by any component, using the {@link #listeners}
45187  * configuration or using the {@link #addListener} method.  Button has a variety of different listeners:
45188  *
45189  * - `click`
45190  * - `toggle`
45191  * - `mouseover`
45192  * - `mouseout`
45193  * - `mouseshow`
45194  * - `menuhide`
45195  * - `menutriggerover`
45196  * - `menutriggerout`
45197  *
45198  * Example usage:
45199  *
45200  *     @example
45201  *     Ext.create('Ext.Button', {
45202  *         text     : 'Button',
45203  *         renderTo : Ext.getBody(),
45204  *         listeners: {
45205  *             click: function() {
45206  *                 // this == the button, as we are in the local scope
45207  *                 this.setText('I was clicked!');
45208  *             },
45209  *             mouseover: function() {
45210  *                 // set a new config which says we moused over, if not already set
45211  *                 if (!this.mousedOver) {
45212  *                     this.mousedOver = true;
45213  *                     alert('You moused over a button!\n\nI wont do this again.');
45214  *                 }
45215  *             }
45216  *         }
45217  *     });
45218  */
45219 Ext.define('Ext.button.Button', {
45220
45221     /* Begin Definitions */
45222     alias: 'widget.button',
45223     extend: 'Ext.Component',
45224
45225     requires: [
45226         'Ext.menu.Manager',
45227         'Ext.util.ClickRepeater',
45228         'Ext.layout.component.Button',
45229         'Ext.util.TextMetrics',
45230         'Ext.util.KeyMap'
45231     ],
45232
45233     alternateClassName: 'Ext.Button',
45234     /* End Definitions */
45235
45236     isButton: true,
45237     componentLayout: 'button',
45238
45239     /**
45240      * @property {Boolean} hidden
45241      * True if this button is hidden. Read-only.
45242      */
45243     hidden: false,
45244
45245     /**
45246      * @property {Boolean} disabled
45247      * True if this button is disabled. Read-only.
45248      */
45249     disabled: false,
45250
45251     /**
45252      * @property {Boolean} pressed
45253      * True if this button is pressed (only if enableToggle = true). Read-only.
45254      */
45255     pressed: false,
45256
45257     /**
45258      * @cfg {String} text
45259      * The button text to be used as innerHTML (html tags are accepted).
45260      */
45261
45262     /**
45263      * @cfg {String} icon
45264      * The path to an image to display in the button (the image will be set as the background-image CSS property of the
45265      * button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
45266      */
45267
45268     /**
45269      * @cfg {Function} handler
45270      * A function called when the button is clicked (can be used instead of click event).
45271      * @cfg {Ext.button.Button} handler.button This button.
45272      * @cfg {Ext.EventObject} handler.e The click event.
45273      */
45274
45275     /**
45276      * @cfg {Number} minWidth
45277      * The minimum width for this button (used to give a set of buttons a common width).
45278      * See also {@link Ext.panel.Panel}.{@link Ext.panel.Panel#minButtonWidth minButtonWidth}.
45279      */
45280
45281     /**
45282      * @cfg {String/Object} tooltip
45283      * The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or
45284      * QuickTips config object.
45285      */
45286
45287     /**
45288      * @cfg {Boolean} [hidden=false]
45289      * True to start hidden.
45290      */
45291
45292     /**
45293      * @cfg {Boolean} [disabled=true]
45294      * True to start disabled.
45295      */
45296
45297     /**
45298      * @cfg {Boolean} [pressed=false]
45299      * True to start pressed (only if enableToggle = true)
45300      */
45301
45302     /**
45303      * @cfg {String} toggleGroup
45304      * The group this toggle button is a member of (only 1 per group can be pressed)
45305      */
45306
45307     /**
45308      * @cfg {Boolean/Object} [repeat=false]
45309      * True to repeat fire the click event while the mouse is down. This can also be a
45310      * {@link Ext.util.ClickRepeater ClickRepeater} config object.
45311      */
45312
45313     /**
45314      * @cfg {Number} tabIndex
45315      * Set a DOM tabIndex for this button.
45316      */
45317
45318     /**
45319      * @cfg {Boolean} [allowDepress=true]
45320      * False to not allow a pressed Button to be depressed. Only valid when {@link #enableToggle} is true.
45321      */
45322
45323     /**
45324      * @cfg {Boolean} [enableToggle=false]
45325      * True to enable pressed/not pressed toggling.
45326      */
45327     enableToggle: false,
45328
45329     /**
45330      * @cfg {Function} toggleHandler
45331      * Function called when a Button with {@link #enableToggle} set to true is clicked.
45332      * @cfg {Ext.button.Button} toggleHandler.button This button.
45333      * @cfg {Boolean} toggleHandler.state The next state of the Button, true means pressed.
45334      */
45335
45336     /**
45337      * @cfg {Ext.menu.Menu/String/Object} menu
45338      * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob.
45339      */
45340
45341     /**
45342      * @cfg {String} menuAlign
45343      * The position to align the menu to (see {@link Ext.Element#alignTo} for more details).
45344      */
45345     menuAlign: 'tl-bl?',
45346
45347     /**
45348      * @cfg {String} textAlign
45349      * The text alignment for this button (center, left, right).
45350      */
45351     textAlign: 'center',
45352
45353     /**
45354      * @cfg {String} overflowText
45355      * If used in a {@link Ext.toolbar.Toolbar Toolbar}, the text to be used if this item is shown in the overflow menu.
45356      * See also {@link Ext.toolbar.Item}.`{@link Ext.toolbar.Item#overflowText overflowText}`.
45357      */
45358
45359     /**
45360      * @cfg {String} iconCls
45361      * A css class which sets a background image to be used as the icon for this button.
45362      */
45363
45364     /**
45365      * @cfg {String} type
45366      * The type of `<input>` to create: submit, reset or button.
45367      */
45368     type: 'button',
45369
45370     /**
45371      * @cfg {String} clickEvent
45372      * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
45373      */
45374     clickEvent: 'click',
45375
45376     /**
45377      * @cfg {Boolean} preventDefault
45378      * True to prevent the default action when the {@link #clickEvent} is processed.
45379      */
45380     preventDefault: true,
45381
45382     /**
45383      * @cfg {Boolean} handleMouseEvents
45384      * False to disable visual cues on mouseover, mouseout and mousedown.
45385      */
45386     handleMouseEvents: true,
45387
45388     /**
45389      * @cfg {String} tooltipType
45390      * The type of tooltip to use. Either 'qtip' for QuickTips or 'title' for title attribute.
45391      */
45392     tooltipType: 'qtip',
45393
45394     /**
45395      * @cfg {String} [baseCls='x-btn']
45396      * The base CSS class to add to all buttons.
45397      */
45398     baseCls: Ext.baseCSSPrefix + 'btn',
45399
45400     /**
45401      * @cfg {String} pressedCls
45402      * The CSS class to add to a button when it is in the pressed state.
45403      */
45404     pressedCls: 'pressed',
45405
45406     /**
45407      * @cfg {String} overCls
45408      * The CSS class to add to a button when it is in the over (hovered) state.
45409      */
45410     overCls: 'over',
45411
45412     /**
45413      * @cfg {String} focusCls
45414      * The CSS class to add to a button when it is in the focussed state.
45415      */
45416     focusCls: 'focus',
45417
45418     /**
45419      * @cfg {String} menuActiveCls
45420      * The CSS class to add to a button when it's menu is active.
45421      */
45422     menuActiveCls: 'menu-active',
45423
45424     /**
45425      * @cfg {String} href
45426      * The URL to visit when the button is clicked. Specifying this config is equivalent to specifying:
45427      *
45428      *     handler: function() { window.location = "http://www.sencha.com" }
45429      */
45430
45431     /**
45432      * @cfg {Object} baseParams
45433      * An object literal of parameters to pass to the url when the {@link #href} property is specified.
45434      */
45435
45436     /**
45437      * @cfg {Object} params
45438      * An object literal of parameters to pass to the url when the {@link #href} property is specified. Any params
45439      * override {@link #baseParams}. New params can be set using the {@link #setParams} method.
45440      */
45441
45442     ariaRole: 'button',
45443
45444     // inherited
45445     renderTpl:
45446         '<em id="{id}-btnWrap" class="{splitCls}">' +
45447             '<tpl if="href">' +
45448                 '<a id="{id}-btnEl" href="{href}" target="{target}"<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="link">' +
45449                     '<span id="{id}-btnInnerEl" class="{baseCls}-inner">' +
45450                         '{text}' +
45451                     '</span>' +
45452                         '<span id="{id}-btnIconEl" class="{baseCls}-icon"></span>' +
45453                 '</a>' +
45454             '</tpl>' +
45455             '<tpl if="!href">' +
45456                 '<button id="{id}-btnEl" type="{type}" hidefocus="true"' +
45457                     // the autocomplete="off" is required to prevent Firefox from remembering
45458                     // the button's disabled state between page reloads.
45459                     '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="button" autocomplete="off">' +
45460                     '<span id="{id}-btnInnerEl" class="{baseCls}-inner" style="{innerSpanStyle}">' +
45461                         '{text}' +
45462                     '</span>' +
45463                     '<span id="{id}-btnIconEl" class="{baseCls}-icon {iconCls}">&#160;</span>' +
45464                 '</button>' +
45465             '</tpl>' +
45466         '</em>' ,
45467
45468     /**
45469      * @cfg {String} scale
45470      * The size of the Button. Three values are allowed:
45471      *
45472      * - 'small' - Results in the button element being 16px high.
45473      * - 'medium' - Results in the button element being 24px high.
45474      * - 'large' - Results in the button element being 32px high.
45475      */
45476     scale: 'small',
45477
45478     /**
45479      * @private
45480      * An array of allowed scales.
45481      */
45482     allowedScales: ['small', 'medium', 'large'],
45483
45484     /**
45485      * @cfg {Object} scope
45486      * The scope (**this** reference) in which the `{@link #handler}` and `{@link #toggleHandler}` is executed.
45487      * Defaults to this Button.
45488      */
45489
45490     /**
45491      * @cfg {String} iconAlign
45492      * The side of the Button box to render the icon. Four values are allowed:
45493      *
45494      * - 'top'
45495      * - 'right'
45496      * - 'bottom'
45497      * - 'left'
45498      */
45499     iconAlign: 'left',
45500
45501     /**
45502      * @cfg {String} arrowAlign
45503      * The side of the Button box to render the arrow if the button has an associated {@link #menu}. Two
45504      * values are allowed:
45505      *
45506      * - 'right'
45507      * - 'bottom'
45508      */
45509     arrowAlign: 'right',
45510
45511     /**
45512      * @cfg {String} arrowCls
45513      * The className used for the inner arrow element if the button has a menu.
45514      */
45515     arrowCls: 'arrow',
45516
45517     /**
45518      * @property {Ext.Template} template
45519      * A {@link Ext.Template Template} used to create the Button's DOM structure.
45520      *
45521      * Instances, or subclasses which need a different DOM structure may provide a different template layout in
45522      * conjunction with an implementation of {@link #getTemplateArgs}.
45523      */
45524
45525     /**
45526      * @cfg {String} cls
45527      * A CSS class string to apply to the button's main element.
45528      */
45529
45530     /**
45531      * @property {Ext.menu.Menu} menu
45532      * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config
45533      * option.
45534      */
45535
45536     /**
45537      * @cfg {Boolean} autoWidth
45538      * By default, if a width is not specified the button will attempt to stretch horizontally to fit its content. If
45539      * the button is being managed by a width sizing layout (hbox, fit, anchor), set this to false to prevent the button
45540      * from doing this automatic sizing.
45541      */
45542
45543     maskOnDisable: false,
45544
45545     // inherit docs
45546     initComponent: function() {
45547         var me = this;
45548         me.callParent(arguments);
45549
45550         me.addEvents(
45551             /**
45552              * @event click
45553              * Fires when this button is clicked
45554              * @param {Ext.button.Button} this
45555              * @param {Event} e The click event
45556              */
45557             'click',
45558
45559             /**
45560              * @event toggle
45561              * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
45562              * @param {Ext.button.Button} this
45563              * @param {Boolean} pressed
45564              */
45565             'toggle',
45566
45567             /**
45568              * @event mouseover
45569              * Fires when the mouse hovers over the button
45570              * @param {Ext.button.Button} this
45571              * @param {Event} e The event object
45572              */
45573             'mouseover',
45574
45575             /**
45576              * @event mouseout
45577              * Fires when the mouse exits the button
45578              * @param {Ext.button.Button} this
45579              * @param {Event} e The event object
45580              */
45581             'mouseout',
45582
45583             /**
45584              * @event menushow
45585              * If this button has a menu, this event fires when it is shown
45586              * @param {Ext.button.Button} this
45587              * @param {Ext.menu.Menu} menu
45588              */
45589             'menushow',
45590
45591             /**
45592              * @event menuhide
45593              * If this button has a menu, this event fires when it is hidden
45594              * @param {Ext.button.Button} this
45595              * @param {Ext.menu.Menu} menu
45596              */
45597             'menuhide',
45598
45599             /**
45600              * @event menutriggerover
45601              * If this button has a menu, this event fires when the mouse enters the menu triggering element
45602              * @param {Ext.button.Button} this
45603              * @param {Ext.menu.Menu} menu
45604              * @param {Event} e
45605              */
45606             'menutriggerover',
45607
45608             /**
45609              * @event menutriggerout
45610              * If this button has a menu, this event fires when the mouse leaves the menu triggering element
45611              * @param {Ext.button.Button} this
45612              * @param {Ext.menu.Menu} menu
45613              * @param {Event} e
45614              */
45615             'menutriggerout'
45616         );
45617
45618         if (me.menu) {
45619             // Flag that we'll have a splitCls
45620             me.split = true;
45621
45622             // retrieve menu by id or instantiate instance if needed
45623             me.menu = Ext.menu.Manager.get(me.menu);
45624             me.menu.ownerCt = me;
45625         }
45626
45627         // Accept url as a synonym for href
45628         if (me.url) {
45629             me.href = me.url;
45630         }
45631
45632         // preventDefault defaults to false for links
45633         if (me.href && !me.hasOwnProperty('preventDefault')) {
45634             me.preventDefault = false;
45635         }
45636
45637         if (Ext.isString(me.toggleGroup)) {
45638             me.enableToggle = true;
45639         }
45640
45641     },
45642
45643     // private
45644     initAria: function() {
45645         this.callParent();
45646         var actionEl = this.getActionEl();
45647         if (this.menu) {
45648             actionEl.dom.setAttribute('aria-haspopup', true);
45649         }
45650     },
45651
45652     // inherit docs
45653     getActionEl: function() {
45654         return this.btnEl;
45655     },
45656
45657     // inherit docs
45658     getFocusEl: function() {
45659         return this.btnEl;
45660     },
45661
45662     // private
45663     setButtonCls: function() {
45664         var me = this,
45665             cls = [],
45666             btnIconEl = me.btnIconEl,
45667             hide = 'x-hide-display';
45668
45669         if (me.useSetClass) {
45670             if (!Ext.isEmpty(me.oldCls)) {
45671                 me.removeClsWithUI(me.oldCls);
45672                 me.removeClsWithUI(me.pressedCls);
45673             }
45674
45675             // Check whether the button has an icon or not, and if it has an icon, what is th alignment
45676             if (me.iconCls || me.icon) {
45677                 if (me.text) {
45678                     cls.push('icon-text-' + me.iconAlign);
45679                 } else {
45680                     cls.push('icon');
45681                 }
45682                 if (btnIconEl) {
45683                     btnIconEl.removeCls(hide);
45684                 }
45685             } else {
45686                 if (me.text) {
45687                     cls.push('noicon');
45688                 }
45689                 if (btnIconEl) {
45690                     btnIconEl.addCls(hide);
45691                 }
45692             }
45693
45694             me.oldCls = cls;
45695             me.addClsWithUI(cls);
45696             me.addClsWithUI(me.pressed ? me.pressedCls : null);
45697         }
45698     },
45699
45700     // private
45701     onRender: function(ct, position) {
45702         // classNames for the button
45703         var me = this,
45704             repeater, btn;
45705
45706         // Apply the renderData to the template args
45707         Ext.applyIf(me.renderData, me.getTemplateArgs());
45708
45709         me.addChildEls('btnEl', 'btnWrap', 'btnInnerEl', 'btnIconEl');
45710
45711         if (me.scale) {
45712             me.ui = me.ui + '-' + me.scale;
45713         }
45714
45715         // Render internal structure
45716         me.callParent(arguments);
45717
45718         // If it is a split button + has a toolip for the arrow
45719         if (me.split && me.arrowTooltip) {
45720             me.arrowEl.dom.setAttribute(me.getTipAttr(), me.arrowTooltip);
45721         }
45722
45723         // Add listeners to the focus and blur events on the element
45724         me.mon(me.btnEl, {
45725             scope: me,
45726             focus: me.onFocus,
45727             blur : me.onBlur
45728         });
45729
45730         // Set btn as a local variable for easy access
45731         btn = me.el;
45732
45733         if (me.icon) {
45734             me.setIcon(me.icon);
45735         }
45736
45737         if (me.iconCls) {
45738             me.setIconCls(me.iconCls);
45739         }
45740
45741         if (me.tooltip) {
45742             me.setTooltip(me.tooltip, true);
45743         }
45744
45745         if (me.textAlign) {
45746             me.setTextAlign(me.textAlign);
45747         }
45748
45749         // Add the mouse events to the button
45750         if (me.handleMouseEvents) {
45751             me.mon(btn, {
45752                 scope: me,
45753                 mouseover: me.onMouseOver,
45754                 mouseout: me.onMouseOut,
45755                 mousedown: me.onMouseDown
45756             });
45757
45758             if (me.split) {
45759                 me.mon(btn, {
45760                     mousemove: me.onMouseMove,
45761                     scope: me
45762                 });
45763             }
45764         }
45765
45766         // Check if the button has a menu
45767         if (me.menu) {
45768             me.mon(me.menu, {
45769                 scope: me,
45770                 show: me.onMenuShow,
45771                 hide: me.onMenuHide
45772             });
45773
45774             me.keyMap = Ext.create('Ext.util.KeyMap', me.el, {
45775                 key: Ext.EventObject.DOWN,
45776                 handler: me.onDownKey,
45777                 scope: me
45778             });
45779         }
45780
45781         // Check if it is a repeat button
45782         if (me.repeat) {
45783             repeater = Ext.create('Ext.util.ClickRepeater', btn, Ext.isObject(me.repeat) ? me.repeat: {});
45784             me.mon(repeater, 'click', me.onRepeatClick, me);
45785         } else {
45786             me.mon(btn, me.clickEvent, me.onClick, me);
45787         }
45788
45789         // Register the button in the toggle manager
45790         Ext.ButtonToggleManager.register(me);
45791     },
45792
45793     /**
45794      * This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used to
45795      * create this Button's DOM structure.
45796      *
45797      * Instances or subclasses which use a different Template to create a different DOM structure may need to provide
45798      * their own implementation of this method.
45799      *
45800      * @return {Object} Substitution data for a Template. The default implementation which provides data for the default
45801      * {@link #template} returns an Object containing the following properties:
45802      * @return {String} return.type The `<button>`'s {@link #type}
45803      * @return {String} return.splitCls A CSS class to determine the presence and position of an arrow icon.
45804      * (`'x-btn-arrow'` or `'x-btn-arrow-bottom'` or `''`)
45805      * @return {String} return.cls A CSS class name applied to the Button's main `<tbody>` element which determines the
45806      * button's scale and icon alignment.
45807      * @return {String} return.text The {@link #text} to display ion the Button.
45808      * @return {Number} return.tabIndex The tab index within the input flow.
45809      */
45810     getTemplateArgs: function() {
45811         var me = this,
45812             persistentPadding = me.getPersistentBtnPadding(),
45813             innerSpanStyle = '';
45814
45815         // Create negative margin offsets to counteract persistent button padding if needed
45816         if (Math.max.apply(Math, persistentPadding) > 0) {
45817             innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
45818                 return -pad + 'px';
45819             }).join(' ');
45820         }
45821
45822         return {
45823             href     : me.getHref(),
45824             target   : me.target || '_blank',
45825             type     : me.type,
45826             splitCls : me.getSplitCls(),
45827             cls      : me.cls,
45828             iconCls  : me.iconCls || '',
45829             text     : me.text || '&#160;',
45830             tabIndex : me.tabIndex,
45831             innerSpanStyle: innerSpanStyle
45832         };
45833     },
45834
45835     /**
45836      * @private
45837      * If there is a configured href for this Button, returns the href with parameters appended.
45838      * @returns The href string with parameters appended.
45839      */
45840     getHref: function() {
45841         var me = this,
45842             params = Ext.apply({}, me.baseParams);
45843
45844         // write baseParams first, then write any params
45845         params = Ext.apply(params, me.params);
45846         return me.href ? Ext.urlAppend(me.href, Ext.Object.toQueryString(params)) : false;
45847     },
45848
45849     /**
45850      * Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.
45851      *
45852      * **Only valid if the Button was originally configured with a {@link #href}**
45853      *
45854      * @param {Object} params Parameters to use in the href URL.
45855      */
45856     setParams: function(params) {
45857         this.params = params;
45858         this.btnEl.dom.href = this.getHref();
45859     },
45860
45861     getSplitCls: function() {
45862         var me = this;
45863         return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
45864     },
45865
45866     // private
45867     afterRender: function() {
45868         var me = this;
45869         me.useSetClass = true;
45870         me.setButtonCls();
45871         me.doc = Ext.getDoc();
45872         this.callParent(arguments);
45873     },
45874
45875     /**
45876      * Sets the CSS class that provides a background image to use as the button's icon. This method also changes the
45877      * value of the {@link #iconCls} config internally.
45878      * @param {String} cls The CSS class providing the icon image
45879      * @return {Ext.button.Button} this
45880      */
45881     setIconCls: function(cls) {
45882         var me = this,
45883             btnIconEl = me.btnIconEl,
45884             oldCls = me.iconCls;
45885             
45886         me.iconCls = cls;
45887         if (btnIconEl) {
45888             // Remove the previous iconCls from the button
45889             btnIconEl.removeCls(oldCls);
45890             btnIconEl.addCls(cls || '');
45891             me.setButtonCls();
45892         }
45893         return me;
45894     },
45895
45896     /**
45897      * Sets the tooltip for this Button.
45898      *
45899      * @param {String/Object} tooltip This may be:
45900      *
45901      *   - **String** : A string to be used as innerHTML (html tags are accepted) to show in a tooltip
45902      *   - **Object** : A configuration object for {@link Ext.tip.QuickTipManager#register}.
45903      *
45904      * @return {Ext.button.Button} this
45905      */
45906     setTooltip: function(tooltip, initial) {
45907         var me = this;
45908
45909         if (me.rendered) {
45910             if (!initial) {
45911                 me.clearTip();
45912             }
45913             if (Ext.isObject(tooltip)) {
45914                 Ext.tip.QuickTipManager.register(Ext.apply({
45915                     target: me.btnEl.id
45916                 },
45917                 tooltip));
45918                 me.tooltip = tooltip;
45919             } else {
45920                 me.btnEl.dom.setAttribute(me.getTipAttr(), tooltip);
45921             }
45922         } else {
45923             me.tooltip = tooltip;
45924         }
45925         return me;
45926     },
45927
45928     /**
45929      * Sets the text alignment for this button.
45930      * @param {String} align The new alignment of the button text. See {@link #textAlign}.
45931      */
45932     setTextAlign: function(align) {
45933         var me = this,
45934             btnEl = me.btnEl;
45935
45936         if (btnEl) {
45937             btnEl.removeCls(me.baseCls + '-' + me.textAlign);
45938             btnEl.addCls(me.baseCls + '-' + align);
45939         }
45940         me.textAlign = align;
45941         return me;
45942     },
45943
45944     getTipAttr: function(){
45945         return this.tooltipType == 'qtip' ? 'data-qtip' : 'title';
45946     },
45947
45948     // private
45949     getRefItems: function(deep){
45950         var menu = this.menu,
45951             items;
45952         
45953         if (menu) {
45954             items = menu.getRefItems(deep);
45955             items.unshift(menu);
45956         }
45957         return items || [];
45958     },
45959
45960     // private
45961     clearTip: function() {
45962         if (Ext.isObject(this.tooltip)) {
45963             Ext.tip.QuickTipManager.unregister(this.btnEl);
45964         }
45965     },
45966
45967     // private
45968     beforeDestroy: function() {
45969         var me = this;
45970         if (me.rendered) {
45971             me.clearTip();
45972         }
45973         if (me.menu && me.destroyMenu !== false) {
45974             Ext.destroy(me.menu);
45975         }
45976         Ext.destroy(me.btnInnerEl, me.repeater);
45977         me.callParent();
45978     },
45979
45980     // private
45981     onDestroy: function() {
45982         var me = this;
45983         if (me.rendered) {
45984             me.doc.un('mouseover', me.monitorMouseOver, me);
45985             me.doc.un('mouseup', me.onMouseUp, me);
45986             delete me.doc;
45987             Ext.ButtonToggleManager.unregister(me);
45988
45989             Ext.destroy(me.keyMap);
45990             delete me.keyMap;
45991         }
45992         me.callParent();
45993     },
45994
45995     /**
45996      * Assigns this Button's click handler
45997      * @param {Function} handler The function to call when the button is clicked
45998      * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed.
45999      * Defaults to this Button.
46000      * @return {Ext.button.Button} this
46001      */
46002     setHandler: function(handler, scope) {
46003         this.handler = handler;
46004         this.scope = scope;
46005         return this;
46006     },
46007
46008     /**
46009      * Sets this Button's text
46010      * @param {String} text The button text
46011      * @return {Ext.button.Button} this
46012      */
46013     setText: function(text) {
46014         var me = this;
46015         me.text = text;
46016         if (me.el) {
46017             me.btnInnerEl.update(text || '&#160;');
46018             me.setButtonCls();
46019         }
46020         me.doComponentLayout();
46021         return me;
46022     },
46023
46024     /**
46025      * Sets the background image (inline style) of the button. This method also changes the value of the {@link #icon}
46026      * config internally.
46027      * @param {String} icon The path to an image to display in the button
46028      * @return {Ext.button.Button} this
46029      */
46030     setIcon: function(icon) {
46031         var me = this,
46032             iconEl = me.btnIconEl;
46033             
46034         me.icon = icon;
46035         if (iconEl) {
46036             iconEl.setStyle('background-image', icon ? 'url(' + icon + ')': '');
46037             me.setButtonCls();
46038         }
46039         return me;
46040     },
46041
46042     /**
46043      * Gets the text for this Button
46044      * @return {String} The button text
46045      */
46046     getText: function() {
46047         return this.text;
46048     },
46049
46050     /**
46051      * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
46052      * @param {Boolean} [state] Force a particular state
46053      * @param {Boolean} [suppressEvent=false] True to stop events being fired when calling this method.
46054      * @return {Ext.button.Button} this
46055      */
46056     toggle: function(state, suppressEvent) {
46057         var me = this;
46058         state = state === undefined ? !me.pressed : !!state;
46059         if (state !== me.pressed) {
46060             if (me.rendered) {
46061                 me[state ? 'addClsWithUI': 'removeClsWithUI'](me.pressedCls);
46062             }
46063             me.btnEl.dom.setAttribute('aria-pressed', state);
46064             me.pressed = state;
46065             if (!suppressEvent) {
46066                 me.fireEvent('toggle', me, state);
46067                 Ext.callback(me.toggleHandler, me.scope || me, [me, state]);
46068             }
46069         }
46070         return me;
46071     },
46072     
46073     maybeShowMenu: function(){
46074         var me = this;
46075         if (me.menu && !me.hasVisibleMenu() && !me.ignoreNextClick) {
46076             me.showMenu();
46077         }
46078     },
46079
46080     /**
46081      * Shows this button's menu (if it has one)
46082      */
46083     showMenu: function() {
46084         var me = this;
46085         if (me.rendered && me.menu) {
46086             if (me.tooltip && me.getTipAttr() != 'title') {
46087                 Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.btnEl);
46088             }
46089             if (me.menu.isVisible()) {
46090                 me.menu.hide();
46091             }
46092
46093             me.menu.showBy(me.el, me.menuAlign);
46094         }
46095         return me;
46096     },
46097
46098     /**
46099      * Hides this button's menu (if it has one)
46100      */
46101     hideMenu: function() {
46102         if (this.hasVisibleMenu()) {
46103             this.menu.hide();
46104         }
46105         return this;
46106     },
46107
46108     /**
46109      * Returns true if the button has a menu and it is visible
46110      * @return {Boolean}
46111      */
46112     hasVisibleMenu: function() {
46113         var menu = this.menu;
46114         return menu && menu.rendered && menu.isVisible();
46115     },
46116
46117     // private
46118     onRepeatClick: function(repeat, e) {
46119         this.onClick(e);
46120     },
46121
46122     // private
46123     onClick: function(e) {
46124         var me = this;
46125         if (me.preventDefault || (me.disabled && me.getHref()) && e) {
46126             e.preventDefault();
46127         }
46128         if (e.button !== 0) {
46129             return;
46130         }
46131         if (!me.disabled) {
46132             me.doToggle();
46133             me.maybeShowMenu();
46134             me.fireHandler(e);
46135         }
46136     },
46137     
46138     fireHandler: function(e){
46139         var me = this,
46140             handler = me.handler;
46141             
46142         me.fireEvent('click', me, e);
46143         if (handler) {
46144             handler.call(me.scope || me, me, e);
46145         }
46146         me.onBlur();
46147     },
46148     
46149     doToggle: function(){
46150         var me = this;
46151         if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) {
46152             me.toggle();
46153         }
46154     },
46155
46156     /**
46157      * @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element.
46158      * The targets are interrogated to see what is being entered from where.
46159      * @param e
46160      */
46161     onMouseOver: function(e) {
46162         var me = this;
46163         if (!me.disabled && !e.within(me.el, true, true)) {
46164             me.onMouseEnter(e);
46165         }
46166     },
46167
46168     /**
46169      * @private
46170      * mouseout handler called when a mouseout event occurs anywhere within the encapsulating element -
46171      * or the mouse leaves the encapsulating element.
46172      * The targets are interrogated to see what is being exited to where.
46173      * @param e
46174      */
46175     onMouseOut: function(e) {
46176         var me = this;
46177         if (!e.within(me.el, true, true)) {
46178             if (me.overMenuTrigger) {
46179                 me.onMenuTriggerOut(e);
46180             }
46181             me.onMouseLeave(e);
46182         }
46183     },
46184
46185     /**
46186      * @private
46187      * mousemove handler called when the mouse moves anywhere within the encapsulating element.
46188      * The position is checked to determine if the mouse is entering or leaving the trigger area. Using
46189      * mousemove to check this is more resource intensive than we'd like, but it is necessary because
46190      * the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out
46191      * events when needed. In the future we should consider making the trigger a separate element that
46192      * is absolutely positioned and sized over the trigger area.
46193      */
46194     onMouseMove: function(e) {
46195         var me = this,
46196             el = me.el,
46197             over = me.overMenuTrigger,
46198             overlap, btnSize;
46199
46200         if (me.split) {
46201             if (me.arrowAlign === 'right') {
46202                 overlap = e.getX() - el.getX();
46203                 btnSize = el.getWidth();
46204             } else {
46205                 overlap = e.getY() - el.getY();
46206                 btnSize = el.getHeight();
46207             }
46208
46209             if (overlap > (btnSize - me.getTriggerSize())) {
46210                 if (!over) {
46211                     me.onMenuTriggerOver(e);
46212                 }
46213             } else {
46214                 if (over) {
46215                     me.onMenuTriggerOut(e);
46216                 }
46217             }
46218         }
46219     },
46220
46221     /**
46222      * @private
46223      * Measures the size of the trigger area for menu and split buttons. Will be a width for
46224      * a right-aligned trigger and a height for a bottom-aligned trigger. Cached after first measurement.
46225      */
46226     getTriggerSize: function() {
46227         var me = this,
46228             size = me.triggerSize,
46229             side, sideFirstLetter, undef;
46230
46231         if (size === undef) {
46232             side = me.arrowAlign;
46233             sideFirstLetter = side.charAt(0);
46234             size = me.triggerSize = me.el.getFrameWidth(sideFirstLetter) + me.btnWrap.getFrameWidth(sideFirstLetter) + (me.frameSize && me.frameSize[side] || 0);
46235         }
46236         return size;
46237     },
46238
46239     /**
46240      * @private
46241      * virtual mouseenter handler called when it is detected that the mouseout event
46242      * signified the mouse entering the encapsulating element.
46243      * @param e
46244      */
46245     onMouseEnter: function(e) {
46246         var me = this;
46247         me.addClsWithUI(me.overCls);
46248         me.fireEvent('mouseover', me, e);
46249     },
46250
46251     /**
46252      * @private
46253      * virtual mouseleave handler called when it is detected that the mouseover event
46254      * signified the mouse entering the encapsulating element.
46255      * @param e
46256      */
46257     onMouseLeave: function(e) {
46258         var me = this;
46259         me.removeClsWithUI(me.overCls);
46260         me.fireEvent('mouseout', me, e);
46261     },
46262
46263     /**
46264      * @private
46265      * virtual mouseenter handler called when it is detected that the mouseover event
46266      * signified the mouse entering the arrow area of the button - the <em>.
46267      * @param e
46268      */
46269     onMenuTriggerOver: function(e) {
46270         var me = this;
46271         me.overMenuTrigger = true;
46272         me.fireEvent('menutriggerover', me, me.menu, e);
46273     },
46274
46275     /**
46276      * @private
46277      * virtual mouseleave handler called when it is detected that the mouseout event
46278      * signified the mouse leaving the arrow area of the button - the <em>.
46279      * @param e
46280      */
46281     onMenuTriggerOut: function(e) {
46282         var me = this;
46283         delete me.overMenuTrigger;
46284         me.fireEvent('menutriggerout', me, me.menu, e);
46285     },
46286
46287     // inherit docs
46288     enable : function(silent) {
46289         var me = this;
46290
46291         me.callParent(arguments);
46292
46293         me.removeClsWithUI('disabled');
46294
46295         return me;
46296     },
46297
46298     // inherit docs
46299     disable : function(silent) {
46300         var me = this;
46301
46302         me.callParent(arguments);
46303
46304         me.addClsWithUI('disabled');
46305         me.removeClsWithUI(me.overCls);
46306
46307         return me;
46308     },
46309
46310     /**
46311      * Method to change the scale of the button. See {@link #scale} for allowed configurations.
46312      * @param {String} scale The scale to change to.
46313      */
46314     setScale: function(scale) {
46315         var me = this,
46316             ui = me.ui.replace('-' + me.scale, '');
46317
46318         //check if it is an allowed scale
46319         if (!Ext.Array.contains(me.allowedScales, scale)) {
46320             throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
46321         }
46322
46323         me.scale = scale;
46324         me.setUI(ui);
46325     },
46326
46327     // inherit docs
46328     setUI: function(ui) {
46329         var me = this;
46330
46331         //we need to append the scale to the UI, if not already done
46332         if (me.scale && !ui.match(me.scale)) {
46333             ui = ui + '-' + me.scale;
46334         }
46335
46336         me.callParent([ui]);
46337
46338         // Set all the state classNames, as they need to include the UI
46339         // me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
46340     },
46341
46342     // private
46343     onFocus: function(e) {
46344         var me = this;
46345         if (!me.disabled) {
46346             me.addClsWithUI(me.focusCls);
46347         }
46348     },
46349
46350     // private
46351     onBlur: function(e) {
46352         var me = this;
46353         me.removeClsWithUI(me.focusCls);
46354     },
46355
46356     // private
46357     onMouseDown: function(e) {
46358         var me = this;
46359         if (!me.disabled && e.button === 0) {
46360             me.addClsWithUI(me.pressedCls);
46361             me.doc.on('mouseup', me.onMouseUp, me);
46362         }
46363     },
46364     // private
46365     onMouseUp: function(e) {
46366         var me = this;
46367         if (e.button === 0) {
46368             if (!me.pressed) {
46369                 me.removeClsWithUI(me.pressedCls);
46370             }
46371             me.doc.un('mouseup', me.onMouseUp, me);
46372         }
46373     },
46374     // private
46375     onMenuShow: function(e) {
46376         var me = this;
46377         me.ignoreNextClick = 0;
46378         me.addClsWithUI(me.menuActiveCls);
46379         me.fireEvent('menushow', me, me.menu);
46380     },
46381
46382     // private
46383     onMenuHide: function(e) {
46384         var me = this;
46385         me.removeClsWithUI(me.menuActiveCls);
46386         me.ignoreNextClick = Ext.defer(me.restoreClick, 250, me);
46387         me.fireEvent('menuhide', me, me.menu);
46388     },
46389
46390     // private
46391     restoreClick: function() {
46392         this.ignoreNextClick = 0;
46393     },
46394
46395     // private
46396     onDownKey: function() {
46397         var me = this;
46398
46399         if (!me.disabled) {
46400             if (me.menu) {
46401                 me.showMenu();
46402             }
46403         }
46404     },
46405
46406     /**
46407      * @private
46408      * Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
46409      * element that cannot be removed. This method returns the size of that padding with a one-time detection.
46410      * @return {Number[]} [top, right, bottom, left]
46411      */
46412     getPersistentBtnPadding: function() {
46413         var cls = Ext.button.Button,
46414             padding = cls.persistentPadding,
46415             btn, leftTop, btnEl, btnInnerEl;
46416
46417         if (!padding) {
46418             padding = cls.persistentPadding = [0, 0, 0, 0]; //set early to prevent recursion
46419
46420             if (!Ext.isIE) { //short-circuit IE as it sometimes gives false positive for padding
46421                 // Create auto-size button offscreen and measure its insides
46422                 btn = Ext.create('Ext.button.Button', {
46423                     renderTo: Ext.getBody(),
46424                     text: 'test',
46425                     style: 'position:absolute;top:-999px;'
46426                 });
46427                 btnEl = btn.btnEl;
46428                 btnInnerEl = btn.btnInnerEl;
46429                 btnEl.setSize(null, null); //clear any hard dimensions on the button el to see what it does naturally
46430
46431                 leftTop = btnInnerEl.getOffsetsTo(btnEl);
46432                 padding[0] = leftTop[1];
46433                 padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
46434                 padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
46435                 padding[3] = leftTop[0];
46436
46437                 btn.destroy();
46438             }
46439         }
46440
46441         return padding;
46442     }
46443
46444 }, function() {
46445     var groups = {};
46446
46447     function toggleGroup(btn, state) {
46448         var g, i, l;
46449         if (state) {
46450             g = groups[btn.toggleGroup];
46451             for (i = 0, l = g.length; i < l; i++) {
46452                 if (g[i] !== btn) {
46453                     g[i].toggle(false);
46454                 }
46455             }
46456         }
46457     }
46458
46459     /**
46460      * Private utility class used by Button
46461      * @hide
46462      */
46463     Ext.ButtonToggleManager = {
46464         register: function(btn) {
46465             if (!btn.toggleGroup) {
46466                 return;
46467             }
46468             var group = groups[btn.toggleGroup];
46469             if (!group) {
46470                 group = groups[btn.toggleGroup] = [];
46471             }
46472             group.push(btn);
46473             btn.on('toggle', toggleGroup);
46474         },
46475
46476         unregister: function(btn) {
46477             if (!btn.toggleGroup) {
46478                 return;
46479             }
46480             var group = groups[btn.toggleGroup];
46481             if (group) {
46482                 Ext.Array.remove(group, btn);
46483                 btn.un('toggle', toggleGroup);
46484             }
46485         },
46486
46487         /**
46488          * Gets the pressed button in the passed group or null
46489          * @param {String} group
46490          * @return {Ext.button.Button}
46491          */
46492         getPressed: function(group) {
46493             var g = groups[group],
46494                 i = 0,
46495                 len;
46496             if (g) {
46497                 for (len = g.length; i < len; i++) {
46498                     if (g[i].pressed === true) {
46499                         return g[i];
46500                     }
46501                 }
46502             }
46503             return null;
46504         }
46505     };
46506 });
46507
46508 /**
46509  * @class Ext.layout.container.boxOverflow.Menu
46510  * @extends Ext.layout.container.boxOverflow.None
46511  * @private
46512  */
46513 Ext.define('Ext.layout.container.boxOverflow.Menu', {
46514
46515     /* Begin Definitions */
46516
46517     extend: 'Ext.layout.container.boxOverflow.None',
46518     requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],
46519     alternateClassName: 'Ext.layout.boxOverflow.Menu',
46520     
46521     /* End Definitions */
46522
46523     /**
46524      * @cfg {String} afterCtCls
46525      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
46526      * which must always be present at the rightmost edge of the Container
46527      */
46528
46529     /**
46530      * @property noItemsMenuText
46531      * @type String
46532      * HTML fragment to render into the toolbar overflow menu if there are no items to display
46533      */
46534     noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items">(None)</div>',
46535
46536     constructor: function(layout) {
46537         var me = this;
46538
46539         me.callParent(arguments);
46540
46541         // Before layout, we need to re-show all items which we may have hidden due to a previous overflow.
46542         layout.beforeLayout = Ext.Function.createInterceptor(layout.beforeLayout, this.clearOverflow, this);
46543
46544         me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-menu-' + layout.parallelAfter;
46545         /**
46546          * @property menuItems
46547          * @type Array
46548          * Array of all items that are currently hidden and should go into the dropdown menu
46549          */
46550         me.menuItems = [];
46551     },
46552     
46553     onRemove: function(comp){
46554         Ext.Array.remove(this.menuItems, comp);
46555     },
46556
46557     handleOverflow: function(calculations, targetSize) {
46558         var me = this,
46559             layout = me.layout,
46560             methodName = 'get' + layout.parallelPrefixCap,
46561             newSize = {},
46562             posArgs = [null, null];
46563
46564         me.callParent(arguments);
46565         this.createMenu(calculations, targetSize);
46566         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
46567         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - me.afterCt[methodName]();
46568
46569         // Center the menuTrigger button.
46570         // TODO: Should we emulate align: 'middle' like this, or should we 'stretchmax' the menuTrigger?
46571         posArgs[layout.perpendicularSizeIndex] = (calculations.meta.maxSize - me.menuTrigger['get' + layout.perpendicularPrefixCap]()) / 2;
46572         me.menuTrigger.setPosition.apply(me.menuTrigger, posArgs);
46573
46574         return { targetSize: newSize };
46575     },
46576
46577     /**
46578      * @private
46579      * Called by the layout, when it determines that there is no overflow.
46580      * Also called as an interceptor to the layout's onLayout method to reshow
46581      * previously hidden overflowing items.
46582      */
46583     clearOverflow: function(calculations, targetSize) {
46584         var me = this,
46585             newWidth = targetSize ? targetSize.width + (me.afterCt ? me.afterCt.getWidth() : 0) : 0,
46586             items = me.menuItems,
46587             i = 0,
46588             length = items.length,
46589             item;
46590
46591         me.hideTrigger();
46592         for (; i < length; i++) {
46593             items[i].show();
46594         }
46595         items.length = 0;
46596
46597         return targetSize ? {
46598             targetSize: {
46599                 height: targetSize.height,
46600                 width : newWidth
46601             }
46602         } : null;
46603     },
46604
46605     /**
46606      * @private
46607      */
46608     showTrigger: function() {
46609         this.menuTrigger.show();
46610     },
46611
46612     /**
46613      * @private
46614      */
46615     hideTrigger: function() {
46616         if (this.menuTrigger !== undefined) {
46617             this.menuTrigger.hide();
46618         }
46619     },
46620
46621     /**
46622      * @private
46623      * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
46624      */
46625     beforeMenuShow: function(menu) {
46626         var me = this,
46627             items = me.menuItems,
46628             i = 0,
46629             len   = items.length,
46630             item,
46631             prev;
46632
46633         var needsSep = function(group, prev){
46634             return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
46635         };
46636
46637         me.clearMenu();
46638         menu.removeAll();
46639
46640         for (; i < len; i++) {
46641             item = items[i];
46642
46643             // Do not show a separator as a first item
46644             if (!i && (item instanceof Ext.toolbar.Separator)) {
46645                 continue;
46646             }
46647             if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
46648                 menu.add('-');
46649             }
46650
46651             me.addComponentToMenu(menu, item);
46652             prev = item;
46653         }
46654
46655         // put something so the menu isn't empty if no compatible items found
46656         if (menu.items.length < 1) {
46657             menu.add(me.noItemsMenuText);
46658         }
46659     },
46660     
46661     /**
46662      * @private
46663      * Returns a menu config for a given component. This config is used to create a menu item
46664      * to be added to the expander menu
46665      * @param {Ext.Component} component The component to create the config for
46666      * @param {Boolean} hideOnClick Passed through to the menu item
46667      */
46668     createMenuConfig : function(component, hideOnClick) {
46669         var config = Ext.apply({}, component.initialConfig),
46670             group  = component.toggleGroup;
46671
46672         Ext.copyTo(config, component, [
46673             'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
46674         ]);
46675
46676         Ext.apply(config, {
46677             text       : component.overflowText || component.text,
46678             hideOnClick: hideOnClick,
46679             destroyMenu: false
46680         });
46681
46682         if (group || component.enableToggle) {
46683             Ext.apply(config, {
46684                 group  : group,
46685                 checked: component.pressed,
46686                 listeners: {
46687                     checkchange: function(item, checked){
46688                         component.toggle(checked);
46689                     }
46690                 }
46691             });
46692         }
46693
46694         delete config.ownerCt;
46695         delete config.xtype;
46696         delete config.id;
46697         return config;
46698     },
46699
46700     /**
46701      * @private
46702      * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
46703      * @param {Ext.menu.Menu} menu The menu to add to
46704      * @param {Ext.Component} component The component to add
46705      */
46706     addComponentToMenu : function(menu, component) {
46707         var me = this;
46708         if (component instanceof Ext.toolbar.Separator) {
46709             menu.add('-');
46710         } else if (component.isComponent) {
46711             if (component.isXType('splitbutton')) {
46712                 menu.add(me.createMenuConfig(component, true));
46713
46714             } else if (component.isXType('button')) {
46715                 menu.add(me.createMenuConfig(component, !component.menu));
46716
46717             } else if (component.isXType('buttongroup')) {
46718                 component.items.each(function(item){
46719                      me.addComponentToMenu(menu, item);
46720                 });
46721             } else {
46722                 menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));
46723             }
46724         }
46725     },
46726
46727     /**
46728      * @private
46729      * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
46730      * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
46731      */
46732     clearMenu : function() {
46733         var menu = this.moreMenu;
46734         if (menu && menu.items) {
46735             menu.items.each(function(item) {
46736                 if (item.menu) {
46737                     delete item.menu;
46738                 }
46739             });
46740         }
46741     },
46742
46743     /**
46744      * @private
46745      * Creates the overflow trigger and menu used when enableOverflow is set to true and the items
46746      * in the layout are too wide to fit in the space available
46747      */
46748     createMenu: function(calculations, targetSize) {
46749         var me = this,
46750             layout = me.layout,
46751             startProp = layout.parallelBefore,
46752             sizeProp = layout.parallelPrefix,
46753             available = targetSize[sizeProp],
46754             boxes = calculations.boxes,
46755             i = 0,
46756             len = boxes.length,
46757             box;
46758
46759         if (!me.menuTrigger) {
46760             me.createInnerElements();
46761
46762             /**
46763              * @private
46764              * @property menu
46765              * @type Ext.menu.Menu
46766              * The expand menu - holds items for every item that cannot be shown
46767              * because the container is currently not large enough.
46768              */
46769             me.menu = Ext.create('Ext.menu.Menu', {
46770                 listeners: {
46771                     scope: me,
46772                     beforeshow: me.beforeMenuShow
46773                 }
46774             });
46775
46776             /**
46777              * @private
46778              * @property menuTrigger
46779              * @type Ext.button.Button
46780              * The expand button which triggers the overflow menu to be shown
46781              */
46782             me.menuTrigger = Ext.create('Ext.button.Button', {
46783                 ownerCt : me.layout.owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
46784                 iconCls : me.layout.owner.menuTriggerCls,
46785                 ui      : layout.owner instanceof Ext.toolbar.Toolbar ? 'default-toolbar' : 'default',
46786                 menu    : me.menu,
46787                 getSplitCls: function() { return '';},
46788                 renderTo: me.afterCt
46789             });
46790         }
46791         me.showTrigger();
46792         available -= me.afterCt.getWidth();
46793
46794         // Hide all items which are off the end, and store them to allow them to be restored
46795         // before each layout operation.
46796         me.menuItems.length = 0;
46797         for (; i < len; i++) {
46798             box = boxes[i];
46799             if (box[startProp] + box[sizeProp] > available) {
46800                 me.menuItems.push(box.component);
46801                 box.component.hide();
46802             }
46803         }
46804     },
46805
46806     /**
46807      * @private
46808      * Creates the beforeCt, innerCt and afterCt elements if they have not already been created
46809      * @param {Ext.container.Container} container The Container attached to this Layout instance
46810      * @param {Ext.Element} target The target Element
46811      */
46812     createInnerElements: function() {
46813         var me = this,
46814             target = me.layout.getRenderTarget();
46815
46816         if (!this.afterCt) {
46817             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
46818             this.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + this.afterCtCls}, 'before');
46819         }
46820     },
46821
46822     /**
46823      * @private
46824      */
46825     destroy: function() {
46826         Ext.destroy(this.menu, this.menuTrigger);
46827     }
46828 });
46829 /**
46830  * This class represents a rectangular region in X,Y space, and performs geometric
46831  * transformations or tests upon the region.
46832  *
46833  * This class may be used to compare the document regions occupied by elements.
46834  */
46835 Ext.define('Ext.util.Region', {
46836
46837     /* Begin Definitions */
46838
46839     requires: ['Ext.util.Offset'],
46840
46841     statics: {
46842         /**
46843          * @static
46844          * Retrieves an Ext.util.Region for a particular element.
46845          * @param {String/HTMLElement/Ext.Element} el An element ID, htmlElement or Ext.Element representing an element in the document.
46846          * @returns {Ext.util.Region} region
46847          */
46848         getRegion: function(el) {
46849             return Ext.fly(el).getPageBox(true);
46850         },
46851
46852         /**
46853          * @static
46854          * Creates a Region from a "box" Object which contains four numeric properties `top`, `right`, `bottom` and `left`.
46855          * @param {Object} o An object with `top`, `right`, `bottom` and `left` properties.
46856          * @return {Ext.util.Region} region The Region constructed based on the passed object
46857          */
46858         from: function(o) {
46859             return new this(o.top, o.right, o.bottom, o.left);
46860         }
46861     },
46862
46863     /* End Definitions */
46864
46865     /**
46866      * Creates a region from the bounding sides.
46867      * @param {Number} top Top The topmost pixel of the Region.
46868      * @param {Number} right Right The rightmost pixel of the Region.
46869      * @param {Number} bottom Bottom The bottom pixel of the Region.
46870      * @param {Number} left Left The leftmost pixel of the Region.
46871      */
46872     constructor : function(t, r, b, l) {
46873         var me = this;
46874         me.y = me.top = me[1] = t;
46875         me.right = r;
46876         me.bottom = b;
46877         me.x = me.left = me[0] = l;
46878     },
46879
46880     /**
46881      * Checks if this region completely contains the region that is passed in.
46882      * @param {Ext.util.Region} region
46883      * @return {Boolean}
46884      */
46885     contains : function(region) {
46886         var me = this;
46887         return (region.x >= me.x &&
46888                 region.right <= me.right &&
46889                 region.y >= me.y &&
46890                 region.bottom <= me.bottom);
46891
46892     },
46893
46894     /**
46895      * Checks if this region intersects the region passed in.
46896      * @param {Ext.util.Region} region
46897      * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection.
46898      */
46899     intersect : function(region) {
46900         var me = this,
46901             t = Math.max(me.y, region.y),
46902             r = Math.min(me.right, region.right),
46903             b = Math.min(me.bottom, region.bottom),
46904             l = Math.max(me.x, region.x);
46905
46906         if (b > t && r > l) {
46907             return new this.self(t, r, b, l);
46908         }
46909         else {
46910             return false;
46911         }
46912     },
46913
46914     /**
46915      * Returns the smallest region that contains the current AND targetRegion.
46916      * @param {Ext.util.Region} region
46917      * @return {Ext.util.Region} a new region
46918      */
46919     union : function(region) {
46920         var me = this,
46921             t = Math.min(me.y, region.y),
46922             r = Math.max(me.right, region.right),
46923             b = Math.max(me.bottom, region.bottom),
46924             l = Math.min(me.x, region.x);
46925
46926         return new this.self(t, r, b, l);
46927     },
46928
46929     /**
46930      * Modifies the current region to be constrained to the targetRegion.
46931      * @param {Ext.util.Region} targetRegion
46932      * @return {Ext.util.Region} this
46933      */
46934     constrainTo : function(r) {
46935         var me = this,
46936             constrain = Ext.Number.constrain;
46937         me.top = me.y = constrain(me.top, r.y, r.bottom);
46938         me.bottom = constrain(me.bottom, r.y, r.bottom);
46939         me.left = me.x = constrain(me.left, r.x, r.right);
46940         me.right = constrain(me.right, r.x, r.right);
46941         return me;
46942     },
46943
46944     /**
46945      * Modifies the current region to be adjusted by offsets.
46946      * @param {Number} top top offset
46947      * @param {Number} right right offset
46948      * @param {Number} bottom bottom offset
46949      * @param {Number} left left offset
46950      * @return {Ext.util.Region} this
46951      */
46952     adjust : function(t, r, b, l) {
46953         var me = this;
46954         me.top = me.y += t;
46955         me.left = me.x += l;
46956         me.right += r;
46957         me.bottom += b;
46958         return me;
46959     },
46960
46961     /**
46962      * Get the offset amount of a point outside the region
46963      * @param {String} [axis]
46964      * @param {Ext.util.Point} [p] the point
46965      * @return {Ext.util.Offset}
46966      */
46967     getOutOfBoundOffset: function(axis, p) {
46968         if (!Ext.isObject(axis)) {
46969             if (axis == 'x') {
46970                 return this.getOutOfBoundOffsetX(p);
46971             } else {
46972                 return this.getOutOfBoundOffsetY(p);
46973             }
46974         } else {
46975             p = axis;
46976             var d = Ext.create('Ext.util.Offset');
46977             d.x = this.getOutOfBoundOffsetX(p.x);
46978             d.y = this.getOutOfBoundOffsetY(p.y);
46979             return d;
46980         }
46981
46982     },
46983
46984     /**
46985      * Get the offset amount on the x-axis
46986      * @param {Number} p the offset
46987      * @return {Number}
46988      */
46989     getOutOfBoundOffsetX: function(p) {
46990         if (p <= this.x) {
46991             return this.x - p;
46992         } else if (p >= this.right) {
46993             return this.right - p;
46994         }
46995
46996         return 0;
46997     },
46998
46999     /**
47000      * Get the offset amount on the y-axis
47001      * @param {Number} p the offset
47002      * @return {Number}
47003      */
47004     getOutOfBoundOffsetY: function(p) {
47005         if (p <= this.y) {
47006             return this.y - p;
47007         } else if (p >= this.bottom) {
47008             return this.bottom - p;
47009         }
47010
47011         return 0;
47012     },
47013
47014     /**
47015      * Check whether the point / offset is out of bound
47016      * @param {String} [axis]
47017      * @param {Ext.util.Point/Number} [p] the point / offset
47018      * @return {Boolean}
47019      */
47020     isOutOfBound: function(axis, p) {
47021         if (!Ext.isObject(axis)) {
47022             if (axis == 'x') {
47023                 return this.isOutOfBoundX(p);
47024             } else {
47025                 return this.isOutOfBoundY(p);
47026             }
47027         } else {
47028             p = axis;
47029             return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
47030         }
47031     },
47032
47033     /**
47034      * Check whether the offset is out of bound in the x-axis
47035      * @param {Number} p the offset
47036      * @return {Boolean}
47037      */
47038     isOutOfBoundX: function(p) {
47039         return (p < this.x || p > this.right);
47040     },
47041
47042     /**
47043      * Check whether the offset is out of bound in the y-axis
47044      * @param {Number} p the offset
47045      * @return {Boolean}
47046      */
47047     isOutOfBoundY: function(p) {
47048         return (p < this.y || p > this.bottom);
47049     },
47050
47051     /**
47052      * Restrict a point within the region by a certain factor.
47053      * @param {String} [axis]
47054      * @param {Ext.util.Point/Ext.util.Offset/Object} [p]
47055      * @param {Number} [factor]
47056      * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
47057      * @private
47058      */
47059     restrict: function(axis, p, factor) {
47060         if (Ext.isObject(axis)) {
47061             var newP;
47062
47063             factor = p;
47064             p = axis;
47065
47066             if (p.copy) {
47067                 newP = p.copy();
47068             }
47069             else {
47070                 newP = {
47071                     x: p.x,
47072                     y: p.y
47073                 };
47074             }
47075
47076             newP.x = this.restrictX(p.x, factor);
47077             newP.y = this.restrictY(p.y, factor);
47078             return newP;
47079         } else {
47080             if (axis == 'x') {
47081                 return this.restrictX(p, factor);
47082             } else {
47083                 return this.restrictY(p, factor);
47084             }
47085         }
47086     },
47087
47088     /**
47089      * Restrict an offset within the region by a certain factor, on the x-axis
47090      * @param {Number} p
47091      * @param {Number} [factor=1] The factor.
47092      * @return {Number}
47093      * @private
47094      */
47095     restrictX : function(p, factor) {
47096         if (!factor) {
47097             factor = 1;
47098         }
47099
47100         if (p <= this.x) {
47101             p -= (p - this.x) * factor;
47102         }
47103         else if (p >= this.right) {
47104             p -= (p - this.right) * factor;
47105         }
47106         return p;
47107     },
47108
47109     /**
47110      * Restrict an offset within the region by a certain factor, on the y-axis
47111      * @param {Number} p
47112      * @param {Number} [factor] The factor, defaults to 1
47113      * @return {Number}
47114      * @private
47115      */
47116     restrictY : function(p, factor) {
47117         if (!factor) {
47118             factor = 1;
47119         }
47120
47121         if (p <= this.y) {
47122             p -= (p - this.y) * factor;
47123         }
47124         else if (p >= this.bottom) {
47125             p -= (p - this.bottom) * factor;
47126         }
47127         return p;
47128     },
47129
47130     /**
47131      * Get the width / height of this region
47132      * @return {Object} an object with width and height properties
47133      * @private
47134      */
47135     getSize: function() {
47136         return {
47137             width: this.right - this.x,
47138             height: this.bottom - this.y
47139         };
47140     },
47141
47142     /**
47143      * Create a copy of this Region.
47144      * @return {Ext.util.Region}
47145      */
47146     copy: function() {
47147         return new this.self(this.y, this.right, this.bottom, this.x);
47148     },
47149
47150     /**
47151      * Copy the values of another Region to this Region
47152      * @param {Ext.util.Region} p The region to copy from.
47153      * @return {Ext.util.Region} This Region
47154      */
47155     copyFrom: function(p) {
47156         var me = this;
47157         me.top = me.y = me[1] = p.y;
47158         me.right = p.right;
47159         me.bottom = p.bottom;
47160         me.left = me.x = me[0] = p.x;
47161
47162         return this;
47163     },
47164
47165     /*
47166      * Dump this to an eye-friendly string, great for debugging
47167      * @return {String}
47168      */
47169     toString: function() {
47170         return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
47171     },
47172
47173     /**
47174      * Translate this region by the given offset amount
47175      * @param {Ext.util.Offset/Object} x Object containing the `x` and `y` properties.
47176      * Or the x value is using the two argument form.
47177      * @param {Number} y The y value unless using an Offset object.
47178      * @return {Ext.util.Region} this This Region
47179      */
47180     translateBy: function(x, y) {
47181         if (arguments.length == 1) {
47182             y = x.y;
47183             x = x.x;
47184         }
47185         var me = this;
47186         me.top = me.y += y;
47187         me.right += x;
47188         me.bottom += y;
47189         me.left = me.x += x;
47190
47191         return me;
47192     },
47193
47194     /**
47195      * Round all the properties of this region
47196      * @return {Ext.util.Region} this This Region
47197      */
47198     round: function() {
47199         var me = this;
47200         me.top = me.y = Math.round(me.y);
47201         me.right = Math.round(me.right);
47202         me.bottom = Math.round(me.bottom);
47203         me.left = me.x = Math.round(me.x);
47204
47205         return me;
47206     },
47207
47208     /**
47209      * Check whether this region is equivalent to the given region
47210      * @param {Ext.util.Region} region The region to compare with
47211      * @return {Boolean}
47212      */
47213     equals: function(region) {
47214         return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left);
47215     }
47216 });
47217
47218 /*
47219  * This is a derivative of the similarly named class in the YUI Library.
47220  * The original license:
47221  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
47222  * Code licensed under the BSD License:
47223  * http://developer.yahoo.net/yui/license.txt
47224  */
47225
47226
47227 /**
47228  * @class Ext.dd.DragDropManager
47229  * DragDropManager is a singleton that tracks the element interaction for
47230  * all DragDrop items in the window.  Generally, you will not call
47231  * this class directly, but it does have helper methods that could
47232  * be useful in your DragDrop implementations.
47233  * @singleton
47234  */
47235 Ext.define('Ext.dd.DragDropManager', {
47236     singleton: true,
47237
47238     requires: ['Ext.util.Region'],
47239
47240     uses: ['Ext.tip.QuickTipManager'],
47241
47242     // shorter ClassName, to save bytes and use internally
47243     alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
47244
47245     /**
47246      * Two dimensional Array of registered DragDrop objects.  The first
47247      * dimension is the DragDrop item group, the second the DragDrop
47248      * object.
47249      * @property ids
47250      * @type String[]
47251      * @private
47252      */
47253     ids: {},
47254
47255     /**
47256      * Array of element ids defined as drag handles.  Used to determine
47257      * if the element that generated the mousedown event is actually the
47258      * handle and not the html element itself.
47259      * @property handleIds
47260      * @type String[]
47261      * @private
47262      */
47263     handleIds: {},
47264
47265     /**
47266      * the DragDrop object that is currently being dragged
47267      * @property {Ext.dd.DragDrop} dragCurrent
47268      * @private
47269      **/
47270     dragCurrent: null,
47271
47272     /**
47273      * the DragDrop object(s) that are being hovered over
47274      * @property {Ext.dd.DragDrop[]} dragOvers
47275      * @private
47276      */
47277     dragOvers: {},
47278
47279     /**
47280      * the X distance between the cursor and the object being dragged
47281      * @property deltaX
47282      * @type Number
47283      * @private
47284      */
47285     deltaX: 0,
47286
47287     /**
47288      * the Y distance between the cursor and the object being dragged
47289      * @property deltaY
47290      * @type Number
47291      * @private
47292      */
47293     deltaY: 0,
47294
47295     /**
47296      * Flag to determine if we should prevent the default behavior of the
47297      * events we define. By default this is true, but this can be set to
47298      * false if you need the default behavior (not recommended)
47299      * @property preventDefault
47300      * @type Boolean
47301      */
47302     preventDefault: true,
47303
47304     /**
47305      * Flag to determine if we should stop the propagation of the events
47306      * we generate. This is true by default but you may want to set it to
47307      * false if the html element contains other features that require the
47308      * mouse click.
47309      * @property stopPropagation
47310      * @type Boolean
47311      */
47312     stopPropagation: true,
47313
47314     /**
47315      * Internal flag that is set to true when drag and drop has been
47316      * intialized
47317      * @property initialized
47318      * @private
47319      */
47320     initialized: false,
47321
47322     /**
47323      * All drag and drop can be disabled.
47324      * @property locked
47325      * @private
47326      */
47327     locked: false,
47328
47329     /**
47330      * Called the first time an element is registered.
47331      * @method init
47332      * @private
47333      */
47334     init: function() {
47335         this.initialized = true;
47336     },
47337
47338     /**
47339      * In point mode, drag and drop interaction is defined by the
47340      * location of the cursor during the drag/drop
47341      * @property POINT
47342      * @type Number
47343      */
47344     POINT: 0,
47345
47346     /**
47347      * In intersect mode, drag and drop interaction is defined by the
47348      * overlap of two or more drag and drop objects.
47349      * @property INTERSECT
47350      * @type Number
47351      */
47352     INTERSECT: 1,
47353
47354     /**
47355      * The current drag and drop mode.  Default: POINT
47356      * @property mode
47357      * @type Number
47358      */
47359     mode: 0,
47360
47361     /**
47362      * Runs method on all drag and drop objects
47363      * @method _execOnAll
47364      * @private
47365      */
47366     _execOnAll: function(sMethod, args) {
47367         for (var i in this.ids) {
47368             for (var j in this.ids[i]) {
47369                 var oDD = this.ids[i][j];
47370                 if (! this.isTypeOfDD(oDD)) {
47371                     continue;
47372                 }
47373                 oDD[sMethod].apply(oDD, args);
47374             }
47375         }
47376     },
47377
47378     /**
47379      * Drag and drop initialization.  Sets up the global event handlers
47380      * @method _onLoad
47381      * @private
47382      */
47383     _onLoad: function() {
47384
47385         this.init();
47386
47387         var Event = Ext.EventManager;
47388         Event.on(document, "mouseup",   this.handleMouseUp, this, true);
47389         Event.on(document, "mousemove", this.handleMouseMove, this, true);
47390         Event.on(window,   "unload",    this._onUnload, this, true);
47391         Event.on(window,   "resize",    this._onResize, this, true);
47392         // Event.on(window,   "mouseout",    this._test);
47393
47394     },
47395
47396     /**
47397      * Reset constraints on all drag and drop objs
47398      * @method _onResize
47399      * @private
47400      */
47401     _onResize: function(e) {
47402         this._execOnAll("resetConstraints", []);
47403     },
47404
47405     /**
47406      * Lock all drag and drop functionality
47407      * @method lock
47408      */
47409     lock: function() { this.locked = true; },
47410
47411     /**
47412      * Unlock all drag and drop functionality
47413      * @method unlock
47414      */
47415     unlock: function() { this.locked = false; },
47416
47417     /**
47418      * Is drag and drop locked?
47419      * @method isLocked
47420      * @return {Boolean} True if drag and drop is locked, false otherwise.
47421      */
47422     isLocked: function() { return this.locked; },
47423
47424     /**
47425      * Location cache that is set for all drag drop objects when a drag is
47426      * initiated, cleared when the drag is finished.
47427      * @property locationCache
47428      * @private
47429      */
47430     locationCache: {},
47431
47432     /**
47433      * Set useCache to false if you want to force object the lookup of each
47434      * drag and drop linked element constantly during a drag.
47435      * @property useCache
47436      * @type Boolean
47437      */
47438     useCache: true,
47439
47440     /**
47441      * The number of pixels that the mouse needs to move after the
47442      * mousedown before the drag is initiated.  Default=3;
47443      * @property clickPixelThresh
47444      * @type Number
47445      */
47446     clickPixelThresh: 3,
47447
47448     /**
47449      * The number of milliseconds after the mousedown event to initiate the
47450      * drag if we don't get a mouseup event. Default=350
47451      * @property clickTimeThresh
47452      * @type Number
47453      */
47454     clickTimeThresh: 350,
47455
47456     /**
47457      * Flag that indicates that either the drag pixel threshold or the
47458      * mousdown time threshold has been met
47459      * @property dragThreshMet
47460      * @type Boolean
47461      * @private
47462      */
47463     dragThreshMet: false,
47464
47465     /**
47466      * Timeout used for the click time threshold
47467      * @property clickTimeout
47468      * @type Object
47469      * @private
47470      */
47471     clickTimeout: null,
47472
47473     /**
47474      * The X position of the mousedown event stored for later use when a
47475      * drag threshold is met.
47476      * @property startX
47477      * @type Number
47478      * @private
47479      */
47480     startX: 0,
47481
47482     /**
47483      * The Y position of the mousedown event stored for later use when a
47484      * drag threshold is met.
47485      * @property startY
47486      * @type Number
47487      * @private
47488      */
47489     startY: 0,
47490
47491     /**
47492      * Each DragDrop instance must be registered with the DragDropManager.
47493      * This is executed in DragDrop.init()
47494      * @method regDragDrop
47495      * @param {Ext.dd.DragDrop} oDD the DragDrop object to register
47496      * @param {String} sGroup the name of the group this element belongs to
47497      */
47498     regDragDrop: function(oDD, sGroup) {
47499         if (!this.initialized) { this.init(); }
47500
47501         if (!this.ids[sGroup]) {
47502             this.ids[sGroup] = {};
47503         }
47504         this.ids[sGroup][oDD.id] = oDD;
47505     },
47506
47507     /**
47508      * Removes the supplied dd instance from the supplied group. Executed
47509      * by DragDrop.removeFromGroup, so don't call this function directly.
47510      * @method removeDDFromGroup
47511      * @private
47512      */
47513     removeDDFromGroup: function(oDD, sGroup) {
47514         if (!this.ids[sGroup]) {
47515             this.ids[sGroup] = {};
47516         }
47517
47518         var obj = this.ids[sGroup];
47519         if (obj && obj[oDD.id]) {
47520             delete obj[oDD.id];
47521         }
47522     },
47523
47524     /**
47525      * Unregisters a drag and drop item.  This is executed in
47526      * DragDrop.unreg, use that method instead of calling this directly.
47527      * @method _remove
47528      * @private
47529      */
47530     _remove: function(oDD) {
47531         for (var g in oDD.groups) {
47532             if (g && this.ids[g] && this.ids[g][oDD.id]) {
47533                 delete this.ids[g][oDD.id];
47534             }
47535         }
47536         delete this.handleIds[oDD.id];
47537     },
47538
47539     /**
47540      * Each DragDrop handle element must be registered.  This is done
47541      * automatically when executing DragDrop.setHandleElId()
47542      * @method regHandle
47543      * @param {String} sDDId the DragDrop id this element is a handle for
47544      * @param {String} sHandleId the id of the element that is the drag
47545      * handle
47546      */
47547     regHandle: function(sDDId, sHandleId) {
47548         if (!this.handleIds[sDDId]) {
47549             this.handleIds[sDDId] = {};
47550         }
47551         this.handleIds[sDDId][sHandleId] = sHandleId;
47552     },
47553
47554     /**
47555      * Utility function to determine if a given element has been
47556      * registered as a drag drop item.
47557      * @method isDragDrop
47558      * @param {String} id the element id to check
47559      * @return {Boolean} true if this element is a DragDrop item,
47560      * false otherwise
47561      */
47562     isDragDrop: function(id) {
47563         return ( this.getDDById(id) ) ? true : false;
47564     },
47565
47566     /**
47567      * Returns the drag and drop instances that are in all groups the
47568      * passed in instance belongs to.
47569      * @method getRelated
47570      * @param {Ext.dd.DragDrop} p_oDD the obj to get related data for
47571      * @param {Boolean} bTargetsOnly if true, only return targetable objs
47572      * @return {Ext.dd.DragDrop[]} the related instances
47573      */
47574     getRelated: function(p_oDD, bTargetsOnly) {
47575         var oDDs = [];
47576         for (var i in p_oDD.groups) {
47577             for (var j in this.ids[i]) {
47578                 var dd = this.ids[i][j];
47579                 if (! this.isTypeOfDD(dd)) {
47580                     continue;
47581                 }
47582                 if (!bTargetsOnly || dd.isTarget) {
47583                     oDDs[oDDs.length] = dd;
47584                 }
47585             }
47586         }
47587
47588         return oDDs;
47589     },
47590
47591     /**
47592      * Returns true if the specified dd target is a legal target for
47593      * the specifice drag obj
47594      * @method isLegalTarget
47595      * @param {Ext.dd.DragDrop} oDD the drag obj
47596      * @param {Ext.dd.DragDrop} oTargetDD the target
47597      * @return {Boolean} true if the target is a legal target for the
47598      * dd obj
47599      */
47600     isLegalTarget: function (oDD, oTargetDD) {
47601         var targets = this.getRelated(oDD, true);
47602         for (var i=0, len=targets.length;i<len;++i) {
47603             if (targets[i].id == oTargetDD.id) {
47604                 return true;
47605             }
47606         }
47607
47608         return false;
47609     },
47610
47611     /**
47612      * My goal is to be able to transparently determine if an object is
47613      * typeof DragDrop, and the exact subclass of DragDrop.  typeof
47614      * returns "object", oDD.constructor.toString() always returns
47615      * "DragDrop" and not the name of the subclass.  So for now it just
47616      * evaluates a well-known variable in DragDrop.
47617      * @method isTypeOfDD
47618      * @param {Object} the object to evaluate
47619      * @return {Boolean} true if typeof oDD = DragDrop
47620      */
47621     isTypeOfDD: function (oDD) {
47622         return (oDD && oDD.__ygDragDrop);
47623     },
47624
47625     /**
47626      * Utility function to determine if a given element has been
47627      * registered as a drag drop handle for the given Drag Drop object.
47628      * @method isHandle
47629      * @param {String} id the element id to check
47630      * @return {Boolean} true if this element is a DragDrop handle, false
47631      * otherwise
47632      */
47633     isHandle: function(sDDId, sHandleId) {
47634         return ( this.handleIds[sDDId] &&
47635                         this.handleIds[sDDId][sHandleId] );
47636     },
47637
47638     /**
47639      * Returns the DragDrop instance for a given id
47640      * @method getDDById
47641      * @param {String} id the id of the DragDrop object
47642      * @return {Ext.dd.DragDrop} the drag drop object, null if it is not found
47643      */
47644     getDDById: function(id) {
47645         for (var i in this.ids) {
47646             if (this.ids[i][id]) {
47647                 return this.ids[i][id];
47648             }
47649         }
47650         return null;
47651     },
47652
47653     /**
47654      * Fired after a registered DragDrop object gets the mousedown event.
47655      * Sets up the events required to track the object being dragged
47656      * @method handleMouseDown
47657      * @param {Event} e the event
47658      * @param {Ext.dd.DragDrop} oDD the DragDrop object being dragged
47659      * @private
47660      */
47661     handleMouseDown: function(e, oDD) {
47662         if(Ext.tip.QuickTipManager){
47663             Ext.tip.QuickTipManager.ddDisable();
47664         }
47665         if(this.dragCurrent){
47666             // the original browser mouseup wasn't handled (e.g. outside FF browser window)
47667             // so clean up first to avoid breaking the next drag
47668             this.handleMouseUp(e);
47669         }
47670
47671         this.currentTarget = e.getTarget();
47672         this.dragCurrent = oDD;
47673
47674         var el = oDD.getEl();
47675
47676         // track start position
47677         this.startX = e.getPageX();
47678         this.startY = e.getPageY();
47679
47680         this.deltaX = this.startX - el.offsetLeft;
47681         this.deltaY = this.startY - el.offsetTop;
47682
47683         this.dragThreshMet = false;
47684
47685         this.clickTimeout = setTimeout(
47686                 function() {
47687                     var DDM = Ext.dd.DragDropManager;
47688                     DDM.startDrag(DDM.startX, DDM.startY);
47689                 },
47690                 this.clickTimeThresh );
47691     },
47692
47693     /**
47694      * Fired when either the drag pixel threshol or the mousedown hold
47695      * time threshold has been met.
47696      * @method startDrag
47697      * @param {Number} x the X position of the original mousedown
47698      * @param {Number} y the Y position of the original mousedown
47699      */
47700     startDrag: function(x, y) {
47701         clearTimeout(this.clickTimeout);
47702         if (this.dragCurrent) {
47703             this.dragCurrent.b4StartDrag(x, y);
47704             this.dragCurrent.startDrag(x, y);
47705         }
47706         this.dragThreshMet = true;
47707     },
47708
47709     /**
47710      * Internal function to handle the mouseup event.  Will be invoked
47711      * from the context of the document.
47712      * @method handleMouseUp
47713      * @param {Event} e the event
47714      * @private
47715      */
47716     handleMouseUp: function(e) {
47717
47718         if(Ext.tip && Ext.tip.QuickTipManager){
47719             Ext.tip.QuickTipManager.ddEnable();
47720         }
47721         if (! this.dragCurrent) {
47722             return;
47723         }
47724
47725         clearTimeout(this.clickTimeout);
47726
47727         if (this.dragThreshMet) {
47728             this.fireEvents(e, true);
47729         } else {
47730         }
47731
47732         this.stopDrag(e);
47733
47734         this.stopEvent(e);
47735     },
47736
47737     /**
47738      * Utility to stop event propagation and event default, if these
47739      * features are turned on.
47740      * @method stopEvent
47741      * @param {Event} e the event as returned by this.getEvent()
47742      */
47743     stopEvent: function(e){
47744         if(this.stopPropagation) {
47745             e.stopPropagation();
47746         }
47747
47748         if (this.preventDefault) {
47749             e.preventDefault();
47750         }
47751     },
47752
47753     /**
47754      * Internal function to clean up event handlers after the drag
47755      * operation is complete
47756      * @method stopDrag
47757      * @param {Event} e the event
47758      * @private
47759      */
47760     stopDrag: function(e) {
47761         // Fire the drag end event for the item that was dragged
47762         if (this.dragCurrent) {
47763             if (this.dragThreshMet) {
47764                 this.dragCurrent.b4EndDrag(e);
47765                 this.dragCurrent.endDrag(e);
47766             }
47767
47768             this.dragCurrent.onMouseUp(e);
47769         }
47770
47771         this.dragCurrent = null;
47772         this.dragOvers = {};
47773     },
47774
47775     /**
47776      * Internal function to handle the mousemove event.  Will be invoked
47777      * from the context of the html element.
47778      *
47779      * @TODO figure out what we can do about mouse events lost when the
47780      * user drags objects beyond the window boundary.  Currently we can
47781      * detect this in internet explorer by verifying that the mouse is
47782      * down during the mousemove event.  Firefox doesn't give us the
47783      * button state on the mousemove event.
47784      * @method handleMouseMove
47785      * @param {Event} e the event
47786      * @private
47787      */
47788     handleMouseMove: function(e) {
47789         if (! this.dragCurrent) {
47790             return true;
47791         }
47792         // var button = e.which || e.button;
47793
47794         // check for IE mouseup outside of page boundary
47795         if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
47796             this.stopEvent(e);
47797             return this.handleMouseUp(e);
47798         }
47799
47800         if (!this.dragThreshMet) {
47801             var diffX = Math.abs(this.startX - e.getPageX());
47802             var diffY = Math.abs(this.startY - e.getPageY());
47803             if (diffX > this.clickPixelThresh ||
47804                         diffY > this.clickPixelThresh) {
47805                 this.startDrag(this.startX, this.startY);
47806             }
47807         }
47808
47809         if (this.dragThreshMet) {
47810             this.dragCurrent.b4Drag(e);
47811             this.dragCurrent.onDrag(e);
47812             if(!this.dragCurrent.moveOnly){
47813                 this.fireEvents(e, false);
47814             }
47815         }
47816
47817         this.stopEvent(e);
47818
47819         return true;
47820     },
47821
47822     /**
47823      * Iterates over all of the DragDrop elements to find ones we are
47824      * hovering over or dropping on
47825      * @method fireEvents
47826      * @param {Event} e the event
47827      * @param {Boolean} isDrop is this a drop op or a mouseover op?
47828      * @private
47829      */
47830     fireEvents: function(e, isDrop) {
47831         var dc = this.dragCurrent;
47832
47833         // If the user did the mouse up outside of the window, we could
47834         // get here even though we have ended the drag.
47835         if (!dc || dc.isLocked()) {
47836             return;
47837         }
47838
47839         var pt = e.getPoint();
47840
47841         // cache the previous dragOver array
47842         var oldOvers = [];
47843
47844         var outEvts   = [];
47845         var overEvts  = [];
47846         var dropEvts  = [];
47847         var enterEvts = [];
47848
47849         // Check to see if the object(s) we were hovering over is no longer
47850         // being hovered over so we can fire the onDragOut event
47851         for (var i in this.dragOvers) {
47852
47853             var ddo = this.dragOvers[i];
47854
47855             if (! this.isTypeOfDD(ddo)) {
47856                 continue;
47857             }
47858
47859             if (! this.isOverTarget(pt, ddo, this.mode)) {
47860                 outEvts.push( ddo );
47861             }
47862
47863             oldOvers[i] = true;
47864             delete this.dragOvers[i];
47865         }
47866
47867         for (var sGroup in dc.groups) {
47868
47869             if ("string" != typeof sGroup) {
47870                 continue;
47871             }
47872
47873             for (i in this.ids[sGroup]) {
47874                 var oDD = this.ids[sGroup][i];
47875                 if (! this.isTypeOfDD(oDD)) {
47876                     continue;
47877                 }
47878
47879                 if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
47880                     if (this.isOverTarget(pt, oDD, this.mode)) {
47881                         // look for drop interactions
47882                         if (isDrop) {
47883                             dropEvts.push( oDD );
47884                         // look for drag enter and drag over interactions
47885                         } else {
47886
47887                             // initial drag over: dragEnter fires
47888                             if (!oldOvers[oDD.id]) {
47889                                 enterEvts.push( oDD );
47890                             // subsequent drag overs: dragOver fires
47891                             } else {
47892                                 overEvts.push( oDD );
47893                             }
47894
47895                             this.dragOvers[oDD.id] = oDD;
47896                         }
47897                     }
47898                 }
47899             }
47900         }
47901
47902         if (this.mode) {
47903             if (outEvts.length) {
47904                 dc.b4DragOut(e, outEvts);
47905                 dc.onDragOut(e, outEvts);
47906             }
47907
47908             if (enterEvts.length) {
47909                 dc.onDragEnter(e, enterEvts);
47910             }
47911
47912             if (overEvts.length) {
47913                 dc.b4DragOver(e, overEvts);
47914                 dc.onDragOver(e, overEvts);
47915             }
47916
47917             if (dropEvts.length) {
47918                 dc.b4DragDrop(e, dropEvts);
47919                 dc.onDragDrop(e, dropEvts);
47920             }
47921
47922         } else {
47923             // fire dragout events
47924             var len = 0;
47925             for (i=0, len=outEvts.length; i<len; ++i) {
47926                 dc.b4DragOut(e, outEvts[i].id);
47927                 dc.onDragOut(e, outEvts[i].id);
47928             }
47929
47930             // fire enter events
47931             for (i=0,len=enterEvts.length; i<len; ++i) {
47932                 // dc.b4DragEnter(e, oDD.id);
47933                 dc.onDragEnter(e, enterEvts[i].id);
47934             }
47935
47936             // fire over events
47937             for (i=0,len=overEvts.length; i<len; ++i) {
47938                 dc.b4DragOver(e, overEvts[i].id);
47939                 dc.onDragOver(e, overEvts[i].id);
47940             }
47941
47942             // fire drop events
47943             for (i=0, len=dropEvts.length; i<len; ++i) {
47944                 dc.b4DragDrop(e, dropEvts[i].id);
47945                 dc.onDragDrop(e, dropEvts[i].id);
47946             }
47947
47948         }
47949
47950         // notify about a drop that did not find a target
47951         if (isDrop && !dropEvts.length) {
47952             dc.onInvalidDrop(e);
47953         }
47954
47955     },
47956
47957     /**
47958      * Helper function for getting the best match from the list of drag
47959      * and drop objects returned by the drag and drop events when we are
47960      * in INTERSECT mode.  It returns either the first object that the
47961      * cursor is over, or the object that has the greatest overlap with
47962      * the dragged element.
47963      * @method getBestMatch
47964      * @param  {Ext.dd.DragDrop[]} dds The array of drag and drop objects
47965      * targeted
47966      * @return {Ext.dd.DragDrop}       The best single match
47967      */
47968     getBestMatch: function(dds) {
47969         var winner = null;
47970         // Return null if the input is not what we expect
47971         //if (!dds || !dds.length || dds.length == 0) {
47972            // winner = null;
47973         // If there is only one item, it wins
47974         //} else if (dds.length == 1) {
47975
47976         var len = dds.length;
47977
47978         if (len == 1) {
47979             winner = dds[0];
47980         } else {
47981             // Loop through the targeted items
47982             for (var i=0; i<len; ++i) {
47983                 var dd = dds[i];
47984                 // If the cursor is over the object, it wins.  If the
47985                 // cursor is over multiple matches, the first one we come
47986                 // to wins.
47987                 if (dd.cursorIsOver) {
47988                     winner = dd;
47989                     break;
47990                 // Otherwise the object with the most overlap wins
47991                 } else {
47992                     if (!winner ||
47993                         winner.overlap.getArea() < dd.overlap.getArea()) {
47994                         winner = dd;
47995                     }
47996                 }
47997             }
47998         }
47999
48000         return winner;
48001     },
48002
48003     /**
48004      * Refreshes the cache of the top-left and bottom-right points of the
48005      * drag and drop objects in the specified group(s).  This is in the
48006      * format that is stored in the drag and drop instance, so typical
48007      * usage is:
48008      * <code>
48009      * Ext.dd.DragDropManager.refreshCache(ddinstance.groups);
48010      * </code>
48011      * Alternatively:
48012      * <code>
48013      * Ext.dd.DragDropManager.refreshCache({group1:true, group2:true});
48014      * </code>
48015      * @TODO this really should be an indexed array.  Alternatively this
48016      * method could accept both.
48017      * @method refreshCache
48018      * @param {Object} groups an associative array of groups to refresh
48019      */
48020     refreshCache: function(groups) {
48021         for (var sGroup in groups) {
48022             if ("string" != typeof sGroup) {
48023                 continue;
48024             }
48025             for (var i in this.ids[sGroup]) {
48026                 var oDD = this.ids[sGroup][i];
48027
48028                 if (this.isTypeOfDD(oDD)) {
48029                 // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
48030                     var loc = this.getLocation(oDD);
48031                     if (loc) {
48032                         this.locationCache[oDD.id] = loc;
48033                     } else {
48034                         delete this.locationCache[oDD.id];
48035                         // this will unregister the drag and drop object if
48036                         // the element is not in a usable state
48037                         // oDD.unreg();
48038                     }
48039                 }
48040             }
48041         }
48042     },
48043
48044     /**
48045      * This checks to make sure an element exists and is in the DOM.  The
48046      * main purpose is to handle cases where innerHTML is used to remove
48047      * drag and drop objects from the DOM.  IE provides an 'unspecified
48048      * error' when trying to access the offsetParent of such an element
48049      * @method verifyEl
48050      * @param {HTMLElement} el the element to check
48051      * @return {Boolean} true if the element looks usable
48052      */
48053     verifyEl: function(el) {
48054         if (el) {
48055             var parent;
48056             if(Ext.isIE){
48057                 try{
48058                     parent = el.offsetParent;
48059                 }catch(e){}
48060             }else{
48061                 parent = el.offsetParent;
48062             }
48063             if (parent) {
48064                 return true;
48065             }
48066         }
48067
48068         return false;
48069     },
48070
48071     /**
48072      * Returns a Region object containing the drag and drop element's position
48073      * and size, including the padding configured for it
48074      * @method getLocation
48075      * @param {Ext.dd.DragDrop} oDD the drag and drop object to get the location for.
48076      * @return {Ext.util.Region} a Region object representing the total area
48077      * the element occupies, including any padding
48078      * the instance is configured for.
48079      */
48080     getLocation: function(oDD) {
48081         if (! this.isTypeOfDD(oDD)) {
48082             return null;
48083         }
48084
48085         //delegate getLocation method to the
48086         //drag and drop target.
48087         if (oDD.getRegion) {
48088             return oDD.getRegion();
48089         }
48090
48091         var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
48092
48093         try {
48094             pos= Ext.Element.getXY(el);
48095         } catch (e) { }
48096
48097         if (!pos) {
48098             return null;
48099         }
48100
48101         x1 = pos[0];
48102         x2 = x1 + el.offsetWidth;
48103         y1 = pos[1];
48104         y2 = y1 + el.offsetHeight;
48105
48106         t = y1 - oDD.padding[0];
48107         r = x2 + oDD.padding[1];
48108         b = y2 + oDD.padding[2];
48109         l = x1 - oDD.padding[3];
48110
48111         return Ext.create('Ext.util.Region', t, r, b, l);
48112     },
48113
48114     /**
48115      * Checks the cursor location to see if it over the target
48116      * @method isOverTarget
48117      * @param {Ext.util.Point} pt The point to evaluate
48118      * @param {Ext.dd.DragDrop} oTarget the DragDrop object we are inspecting
48119      * @return {Boolean} true if the mouse is over the target
48120      * @private
48121      */
48122     isOverTarget: function(pt, oTarget, intersect) {
48123         // use cache if available
48124         var loc = this.locationCache[oTarget.id];
48125         if (!loc || !this.useCache) {
48126             loc = this.getLocation(oTarget);
48127             this.locationCache[oTarget.id] = loc;
48128
48129         }
48130
48131         if (!loc) {
48132             return false;
48133         }
48134
48135         oTarget.cursorIsOver = loc.contains( pt );
48136
48137         // DragDrop is using this as a sanity check for the initial mousedown
48138         // in this case we are done.  In POINT mode, if the drag obj has no
48139         // contraints, we are also done. Otherwise we need to evaluate the
48140         // location of the target as related to the actual location of the
48141         // dragged element.
48142         var dc = this.dragCurrent;
48143         if (!dc || !dc.getTargetCoord ||
48144                 (!intersect && !dc.constrainX && !dc.constrainY)) {
48145             return oTarget.cursorIsOver;
48146         }
48147
48148         oTarget.overlap = null;
48149
48150         // Get the current location of the drag element, this is the
48151         // location of the mouse event less the delta that represents
48152         // where the original mousedown happened on the element.  We
48153         // need to consider constraints and ticks as well.
48154         var pos = dc.getTargetCoord(pt.x, pt.y);
48155
48156         var el = dc.getDragEl();
48157         var curRegion = Ext.create('Ext.util.Region', pos.y,
48158                                                pos.x + el.offsetWidth,
48159                                                pos.y + el.offsetHeight,
48160                                                pos.x );
48161
48162         var overlap = curRegion.intersect(loc);
48163
48164         if (overlap) {
48165             oTarget.overlap = overlap;
48166             return (intersect) ? true : oTarget.cursorIsOver;
48167         } else {
48168             return false;
48169         }
48170     },
48171
48172     /**
48173      * unload event handler
48174      * @method _onUnload
48175      * @private
48176      */
48177     _onUnload: function(e, me) {
48178         Ext.dd.DragDropManager.unregAll();
48179     },
48180
48181     /**
48182      * Cleans up the drag and drop events and objects.
48183      * @method unregAll
48184      * @private
48185      */
48186     unregAll: function() {
48187
48188         if (this.dragCurrent) {
48189             this.stopDrag();
48190             this.dragCurrent = null;
48191         }
48192
48193         this._execOnAll("unreg", []);
48194
48195         for (var i in this.elementCache) {
48196             delete this.elementCache[i];
48197         }
48198
48199         this.elementCache = {};
48200         this.ids = {};
48201     },
48202
48203     /**
48204      * A cache of DOM elements
48205      * @property elementCache
48206      * @private
48207      */
48208     elementCache: {},
48209
48210     /**
48211      * Get the wrapper for the DOM element specified
48212      * @method getElWrapper
48213      * @param {String} id the id of the element to get
48214      * @return {Ext.dd.DragDropManager.ElementWrapper} the wrapped element
48215      * @private
48216      * @deprecated This wrapper isn't that useful
48217      */
48218     getElWrapper: function(id) {
48219         var oWrapper = this.elementCache[id];
48220         if (!oWrapper || !oWrapper.el) {
48221             oWrapper = this.elementCache[id] =
48222                 new this.ElementWrapper(Ext.getDom(id));
48223         }
48224         return oWrapper;
48225     },
48226
48227     /**
48228      * Returns the actual DOM element
48229      * @method getElement
48230      * @param {String} id the id of the elment to get
48231      * @return {Object} The element
48232      * @deprecated use Ext.lib.Ext.getDom instead
48233      */
48234     getElement: function(id) {
48235         return Ext.getDom(id);
48236     },
48237
48238     /**
48239      * Returns the style property for the DOM element (i.e.,
48240      * document.getElById(id).style)
48241      * @method getCss
48242      * @param {String} id the id of the elment to get
48243      * @return {Object} The style property of the element
48244      */
48245     getCss: function(id) {
48246         var el = Ext.getDom(id);
48247         return (el) ? el.style : null;
48248     },
48249
48250     /**
48251      * @class Ext.dd.DragDropManager.ElementWrapper
48252      * Inner class for cached elements
48253      * @private
48254      * @deprecated
48255      */
48256     ElementWrapper: function(el) {
48257         /**
48258          * The element
48259          * @property el
48260          */
48261         this.el = el || null;
48262         /**
48263          * The element id
48264          * @property id
48265          */
48266         this.id = this.el && el.id;
48267         /**
48268          * A reference to the style property
48269          * @property css
48270          */
48271         this.css = this.el && el.style;
48272     },
48273
48274     // The DragDropManager class continues
48275     /** @class Ext.dd.DragDropManager */
48276
48277     /**
48278      * Returns the X position of an html element
48279      * @param {HTMLElement} el the element for which to get the position
48280      * @return {Number} the X coordinate
48281      */
48282     getPosX: function(el) {
48283         return Ext.Element.getX(el);
48284     },
48285
48286     /**
48287      * Returns the Y position of an html element
48288      * @param {HTMLElement} el the element for which to get the position
48289      * @return {Number} the Y coordinate
48290      */
48291     getPosY: function(el) {
48292         return Ext.Element.getY(el);
48293     },
48294
48295     /**
48296      * Swap two nodes.  In IE, we use the native method, for others we
48297      * emulate the IE behavior
48298      * @param {HTMLElement} n1 the first node to swap
48299      * @param {HTMLElement} n2 the other node to swap
48300      */
48301     swapNode: function(n1, n2) {
48302         if (n1.swapNode) {
48303             n1.swapNode(n2);
48304         } else {
48305             var p = n2.parentNode;
48306             var s = n2.nextSibling;
48307
48308             if (s == n1) {
48309                 p.insertBefore(n1, n2);
48310             } else if (n2 == n1.nextSibling) {
48311                 p.insertBefore(n2, n1);
48312             } else {
48313                 n1.parentNode.replaceChild(n2, n1);
48314                 p.insertBefore(n1, s);
48315             }
48316         }
48317     },
48318
48319     /**
48320      * Returns the current scroll position
48321      * @private
48322      */
48323     getScroll: function () {
48324         var doc   = window.document,
48325             docEl = doc.documentElement,
48326             body  = doc.body,
48327             top   = 0,
48328             left  = 0;
48329
48330         if (Ext.isGecko4) {
48331             top  = window.scrollYOffset;
48332             left = window.scrollXOffset;
48333         } else {
48334             if (docEl && (docEl.scrollTop || docEl.scrollLeft)) {
48335                 top  = docEl.scrollTop;
48336                 left = docEl.scrollLeft;
48337             } else if (body) {
48338                 top  = body.scrollTop;
48339                 left = body.scrollLeft;
48340             }
48341         }
48342         return {
48343             top: top,
48344             left: left
48345         };
48346     },
48347
48348     /**
48349      * Returns the specified element style property
48350      * @param {HTMLElement} el          the element
48351      * @param {String}      styleProp   the style property
48352      * @return {String} The value of the style property
48353      */
48354     getStyle: function(el, styleProp) {
48355         return Ext.fly(el).getStyle(styleProp);
48356     },
48357
48358     /**
48359      * Gets the scrollTop
48360      * @return {Number} the document's scrollTop
48361      */
48362     getScrollTop: function () {
48363         return this.getScroll().top;
48364     },
48365
48366     /**
48367      * Gets the scrollLeft
48368      * @return {Number} the document's scrollTop
48369      */
48370     getScrollLeft: function () {
48371         return this.getScroll().left;
48372     },
48373
48374     /**
48375      * Sets the x/y position of an element to the location of the
48376      * target element.
48377      * @param {HTMLElement} moveEl      The element to move
48378      * @param {HTMLElement} targetEl    The position reference element
48379      */
48380     moveToEl: function (moveEl, targetEl) {
48381         var aCoord = Ext.Element.getXY(targetEl);
48382         Ext.Element.setXY(moveEl, aCoord);
48383     },
48384
48385     /**
48386      * Numeric array sort function
48387      * @param {Number} a
48388      * @param {Number} b
48389      * @returns {Number} positive, negative or 0
48390      */
48391     numericSort: function(a, b) {
48392         return (a - b);
48393     },
48394
48395     /**
48396      * Internal counter
48397      * @property {Number} _timeoutCount
48398      * @private
48399      */
48400     _timeoutCount: 0,
48401
48402     /**
48403      * Trying to make the load order less important.  Without this we get
48404      * an error if this file is loaded before the Event Utility.
48405      * @private
48406      */
48407     _addListeners: function() {
48408         if ( document ) {
48409             this._onLoad();
48410         } else {
48411             if (this._timeoutCount > 2000) {
48412             } else {
48413                 setTimeout(this._addListeners, 10);
48414                 if (document && document.body) {
48415                     this._timeoutCount += 1;
48416                 }
48417             }
48418         }
48419     },
48420
48421     /**
48422      * Recursively searches the immediate parent and all child nodes for
48423      * the handle element in order to determine wheter or not it was
48424      * clicked.
48425      * @param {HTMLElement} node the html element to inspect
48426      */
48427     handleWasClicked: function(node, id) {
48428         if (this.isHandle(id, node.id)) {
48429             return true;
48430         } else {
48431             // check to see if this is a text node child of the one we want
48432             var p = node.parentNode;
48433
48434             while (p) {
48435                 if (this.isHandle(id, p.id)) {
48436                     return true;
48437                 } else {
48438                     p = p.parentNode;
48439                 }
48440             }
48441         }
48442
48443         return false;
48444     }
48445 }, function() {
48446     this._addListeners();
48447 });
48448
48449 /**
48450  * @class Ext.layout.container.Box
48451  * @extends Ext.layout.container.Container
48452  * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
48453  */
48454
48455 Ext.define('Ext.layout.container.Box', {
48456
48457     /* Begin Definitions */
48458
48459     alias: ['layout.box'],
48460     extend: 'Ext.layout.container.Container',
48461     alternateClassName: 'Ext.layout.BoxLayout',
48462
48463     requires: [
48464         'Ext.layout.container.boxOverflow.None',
48465         'Ext.layout.container.boxOverflow.Menu',
48466         'Ext.layout.container.boxOverflow.Scroller',
48467         'Ext.util.Format',
48468         'Ext.dd.DragDropManager'
48469     ],
48470
48471     /* End Definitions */
48472
48473     /**
48474      * @cfg {Boolean/Number/Object} animate
48475      * <p>If truthy, child Component are <i>animated</i> into position whenever the Container
48476      * is layed out. If this option is numeric, it is used as the animation duration in milliseconds.</p>
48477      * <p>May be set as a property at any time.</p>
48478      */
48479
48480     /**
48481      * @cfg {Object} defaultMargins
48482      * <p>If the individual contained items do not have a <tt>margins</tt>
48483      * property specified or margin specified via CSS, the default margins from this property will be
48484      * applied to each item.</p>
48485      * <br><p>This property may be specified as an object containing margins
48486      * to apply in the format:</p><pre><code>
48487 {
48488     top: (top margin),
48489     right: (right margin),
48490     bottom: (bottom margin),
48491     left: (left margin)
48492 }</code></pre>
48493      * <p>This property may also be specified as a string containing
48494      * space-separated, numeric margin values. The order of the sides associated
48495      * with each value matches the way CSS processes margin values:</p>
48496      * <div class="mdetail-params"><ul>
48497      * <li>If there is only one value, it applies to all sides.</li>
48498      * <li>If there are two values, the top and bottom borders are set to the
48499      * first value and the right and left are set to the second.</li>
48500      * <li>If there are three values, the top is set to the first value, the left
48501      * and right are set to the second, and the bottom is set to the third.</li>
48502      * <li>If there are four values, they apply to the top, right, bottom, and
48503      * left, respectively.</li>
48504      * </ul></div>
48505      */
48506     defaultMargins: {
48507         top: 0,
48508         right: 0,
48509         bottom: 0,
48510         left: 0
48511     },
48512
48513     /**
48514      * @cfg {String} padding
48515      * <p>Sets the padding to be applied to all child items managed by this layout.</p>
48516      * <p>This property must be specified as a string containing
48517      * space-separated, numeric padding values. The order of the sides associated
48518      * with each value matches the way CSS processes padding values:</p>
48519      * <div class="mdetail-params"><ul>
48520      * <li>If there is only one value, it applies to all sides.</li>
48521      * <li>If there are two values, the top and bottom borders are set to the
48522      * first value and the right and left are set to the second.</li>
48523      * <li>If there are three values, the top is set to the first value, the left
48524      * and right are set to the second, and the bottom is set to the third.</li>
48525      * <li>If there are four values, they apply to the top, right, bottom, and
48526      * left, respectively.</li>
48527      * </ul></div>
48528      */
48529     padding: '0',
48530     // documented in subclasses
48531     pack: 'start',
48532
48533     /**
48534      * @cfg {String} pack
48535      * Controls how the child items of the container are packed together. Acceptable configuration values
48536      * for this property are:
48537      * <div class="mdetail-params"><ul>
48538      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
48539      * <b>left</b> side of container</div></li>
48540      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
48541      * <b>mid-width</b> of container</div></li>
48542      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
48543      * side of container</div></li>
48544      * </ul></div>
48545      */
48546     /**
48547      * @cfg {Number} flex
48548      * This configuration option is to be applied to <b>child <tt>items</tt></b> of the container managed
48549      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
48550      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
48551      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
48552      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
48553      */
48554
48555     type: 'box',
48556     scrollOffset: 0,
48557     itemCls: Ext.baseCSSPrefix + 'box-item',
48558     targetCls: Ext.baseCSSPrefix + 'box-layout-ct',
48559     innerCls: Ext.baseCSSPrefix + 'box-inner',
48560
48561     bindToOwnerCtContainer: true,
48562
48563     // availableSpaceOffset is used to adjust the availableWidth, typically used
48564     // to reserve space for a scrollbar
48565     availableSpaceOffset: 0,
48566
48567     // whether or not to reserve the availableSpaceOffset in layout calculations
48568     reserveOffset: true,
48569
48570     /**
48571      * @cfg {Boolean} shrinkToFit
48572      * True (the default) to allow fixed size components to shrink (limited to their
48573      * minimum size) to avoid overflow. False to preserve fixed sizes even if they cause
48574      * overflow.
48575      */
48576     shrinkToFit: true,
48577
48578     /**
48579      * @cfg {Boolean} clearInnerCtOnLayout
48580      */
48581     clearInnerCtOnLayout: false,
48582
48583     flexSortFn: function (a, b) {
48584         var maxParallelPrefix = 'max' + this.parallelPrefixCap,
48585             infiniteValue = Infinity;
48586         a = a.component[maxParallelPrefix] || infiniteValue;
48587         b = b.component[maxParallelPrefix] || infiniteValue;
48588         // IE 6/7 Don't like Infinity - Infinity...
48589         if (!isFinite(a) && !isFinite(b)) {
48590             return false;
48591         }
48592         return a - b;
48593     },
48594
48595     // Sort into *descending* order.
48596     minSizeSortFn: function(a, b) {
48597         return b.available - a.available;
48598     },
48599
48600     constructor: function(config) {
48601         var me = this;
48602
48603         me.callParent(arguments);
48604
48605         // The sort function needs access to properties in this, so must be bound.
48606         me.flexSortFn = Ext.Function.bind(me.flexSortFn, me);
48607
48608         me.initOverflowHandler();
48609     },
48610
48611     /**
48612      * @private
48613      * Returns the current size and positioning of the passed child item.
48614      * @param {Ext.Component} child The child Component to calculate the box for
48615      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
48616      */
48617     getChildBox: function(child) {
48618         child = child.el || this.owner.getComponent(child).el;
48619         var size = child.getBox(false, true);
48620         return {
48621             left: size.left,
48622             top: size.top,
48623             width: size.width,
48624             height: size.height
48625         };
48626     },
48627
48628     /**
48629      * @private
48630      * Calculates the size and positioning of the passed child item.
48631      * @param {Ext.Component} child The child Component to calculate the box for
48632      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
48633      */
48634     calculateChildBox: function(child) {
48635         var me = this,
48636             boxes = me.calculateChildBoxes(me.getVisibleItems(), me.getLayoutTargetSize()).boxes,
48637             ln = boxes.length,
48638             i = 0;
48639
48640         child = me.owner.getComponent(child);
48641         for (; i < ln; i++) {
48642             if (boxes[i].component === child) {
48643                 return boxes[i];
48644             }
48645         }
48646     },
48647
48648     /**
48649      * @private
48650      * Calculates the size and positioning of each item in the box. This iterates over all of the rendered,
48651      * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
48652      * returns meta data such as maxSize which are useful when resizing layout wrappers such as this.innerCt.
48653      * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
48654      * @param {Object} targetSize Object containing target size and height
48655      * @return {Object} Object containing box measurements for each child, plus meta data
48656      */
48657     calculateChildBoxes: function(visibleItems, targetSize) {
48658         var me = this,
48659             math = Math,
48660             mmax = math.max,
48661             infiniteValue = Infinity,
48662             undefinedValue,
48663
48664             parallelPrefix = me.parallelPrefix,
48665             parallelPrefixCap = me.parallelPrefixCap,
48666             perpendicularPrefix = me.perpendicularPrefix,
48667             perpendicularPrefixCap = me.perpendicularPrefixCap,
48668             parallelMinString = 'min' + parallelPrefixCap,
48669             perpendicularMinString = 'min' + perpendicularPrefixCap,
48670             perpendicularMaxString = 'max' + perpendicularPrefixCap,
48671
48672             parallelSize = targetSize[parallelPrefix] - me.scrollOffset,
48673             perpendicularSize = targetSize[perpendicularPrefix],
48674             padding = me.padding,
48675             parallelOffset = padding[me.parallelBefore],
48676             paddingParallel = parallelOffset + padding[me.parallelAfter],
48677             perpendicularOffset = padding[me.perpendicularLeftTop],
48678             paddingPerpendicular =  perpendicularOffset + padding[me.perpendicularRightBottom],
48679             availPerpendicularSize = mmax(0, perpendicularSize - paddingPerpendicular),
48680
48681             innerCtBorderWidth = me.innerCt.getBorderWidth(me.perpendicularLT + me.perpendicularRB),
48682
48683             isStart = me.pack == 'start',
48684             isCenter = me.pack == 'center',
48685             isEnd = me.pack == 'end',
48686
48687             constrain = Ext.Number.constrain,
48688             visibleCount = visibleItems.length,
48689             nonFlexSize = 0,
48690             totalFlex = 0,
48691             desiredSize = 0,
48692             minimumSize = 0,
48693             maxSize = 0,
48694             boxes = [],
48695             minSizes = [],
48696             calculatedWidth,
48697
48698             i, child, childParallel, childPerpendicular, childMargins, childSize, minParallel, tmpObj, shortfall,
48699             tooNarrow, availableSpace, minSize, item, length, itemIndex, box, oldSize, newSize, reduction, diff,
48700             flexedBoxes, remainingSpace, remainingFlex, flexedSize, parallelMargins, calcs, offset,
48701             perpendicularMargins, stretchSize;
48702
48703         //gather the total flex of all flexed items and the width taken up by fixed width items
48704         for (i = 0; i < visibleCount; i++) {
48705             child = visibleItems[i];
48706             childPerpendicular = child[perpendicularPrefix];
48707             if (!child.flex || !(me.align == 'stretch' || me.align == 'stretchmax')) {
48708                 if (child.componentLayout.initialized !== true) {
48709                     me.layoutItem(child);
48710                 }
48711             }
48712
48713             childMargins = child.margins;
48714             parallelMargins = childMargins[me.parallelBefore] + childMargins[me.parallelAfter];
48715
48716             // Create the box description object for this child item.
48717             tmpObj = {
48718                 component: child,
48719                 margins: childMargins
48720             };
48721
48722             // flex and not 'auto' width
48723             if (child.flex) {
48724                 totalFlex += child.flex;
48725                 childParallel = undefinedValue;
48726             }
48727             // Not flexed or 'auto' width or undefined width
48728             else {
48729                 if (!(child[parallelPrefix] && childPerpendicular)) {
48730                     childSize = child.getSize();
48731                 }
48732                 childParallel = child[parallelPrefix] || childSize[parallelPrefix];
48733                 childPerpendicular = childPerpendicular || childSize[perpendicularPrefix];
48734             }
48735
48736             nonFlexSize += parallelMargins + (childParallel || 0);
48737             desiredSize += parallelMargins + (child.flex ? child[parallelMinString] || 0 : childParallel);
48738             minimumSize += parallelMargins + (child[parallelMinString] || childParallel || 0);
48739
48740             // Max height for align - force layout of non-laid out subcontainers without a numeric height
48741             if (typeof childPerpendicular != 'number') {
48742                 // Clear any static sizing and revert to flow so we can get a proper measurement
48743                 // child['set' + perpendicularPrefixCap](null);
48744                 childPerpendicular = child['get' + perpendicularPrefixCap]();
48745             }
48746
48747             // Track the maximum perpendicular size for use by the stretch and stretchmax align config values.
48748             // Ensure that the tracked maximum perpendicular size takes into account child min[Width|Height] settings!
48749             maxSize = mmax(maxSize, mmax(childPerpendicular, child[perpendicularMinString]||0) + childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom]);
48750
48751             tmpObj[parallelPrefix] = childParallel || undefinedValue;
48752             tmpObj.dirtySize = child.componentLayout.lastComponentSize ? (tmpObj[parallelPrefix] !== child.componentLayout.lastComponentSize[parallelPrefix]) : false;
48753             tmpObj[perpendicularPrefix] = childPerpendicular || undefinedValue;
48754             boxes.push(tmpObj);
48755         }
48756
48757         // Only calculate parallel overflow indicators if we are not auto sizing
48758         if (!me.autoSize) {
48759             shortfall = desiredSize - parallelSize;
48760             tooNarrow = minimumSize > parallelSize;
48761         }
48762
48763         //the space available to the flexed items
48764         availableSpace = mmax(0, parallelSize - nonFlexSize - paddingParallel - (me.reserveOffset ? me.availableSpaceOffset : 0));
48765
48766         if (tooNarrow) {
48767             for (i = 0; i < visibleCount; i++) {
48768                 box = boxes[i];
48769                 minSize = visibleItems[i][parallelMinString] || visibleItems[i][parallelPrefix] || box[parallelPrefix];
48770                 box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
48771                 box[parallelPrefix] = minSize;
48772             }
48773         }
48774         else {
48775             //all flexed items should be sized to their minimum size, other items should be shrunk down until
48776             //the shortfall has been accounted for
48777             if (shortfall > 0) {
48778                 /*
48779                  * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item.
48780                  * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored.
48781                  * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall.
48782                  */
48783                 for (i = 0; i < visibleCount; i++) {
48784                     item = visibleItems[i];
48785                     minSize = item[parallelMinString] || 0;
48786
48787                     //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
48788                     //shrunk to their minSize because they're flexible and should be the first to lose size
48789                     if (item.flex) {
48790                         box = boxes[i];
48791                         box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
48792                         box[parallelPrefix] = minSize;
48793                     } else if (me.shrinkToFit) {
48794                         minSizes.push({
48795                             minSize: minSize,
48796                             available: boxes[i][parallelPrefix] - minSize,
48797                             index: i
48798                         });
48799                     }
48800                 }
48801
48802                 //sort by descending amount of width remaining before minWidth is reached
48803                 Ext.Array.sort(minSizes, me.minSizeSortFn);
48804
48805                 /*
48806                  * Distribute the shortfall (difference between total desired size of all items and actual size available)
48807                  * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
48808                  * smallest difference between their size and minSize first, so that if reducing the size by the average
48809                  * amount would make that item less than its minSize, we carry the remainder over to the next item.
48810                  */
48811                 for (i = 0, length = minSizes.length; i < length; i++) {
48812                     itemIndex = minSizes[i].index;
48813
48814                     if (itemIndex == undefinedValue) {
48815                         continue;
48816                     }
48817                     item = visibleItems[itemIndex];
48818                     minSize = minSizes[i].minSize;
48819
48820                     box = boxes[itemIndex];
48821                     oldSize = box[parallelPrefix];
48822                     newSize = mmax(minSize, oldSize - math.ceil(shortfall / (length - i)));
48823                     reduction = oldSize - newSize;
48824
48825                     box.dirtySize = box.dirtySize || box[parallelPrefix] != newSize;
48826                     box[parallelPrefix] = newSize;
48827                     shortfall -= reduction;
48828                 }
48829                 tooNarrow = (shortfall > 0);
48830             }
48831             else {
48832                 remainingSpace = availableSpace;
48833                 remainingFlex = totalFlex;
48834                 flexedBoxes = [];
48835
48836                 // Create an array containing *just the flexed boxes* for allocation of remainingSpace
48837                 for (i = 0; i < visibleCount; i++) {
48838                     child = visibleItems[i];
48839                     if (isStart && child.flex) {
48840                         flexedBoxes.push(boxes[Ext.Array.indexOf(visibleItems, child)]);
48841                     }
48842                 }
48843                 // The flexed boxes need to be sorted in ascending order of maxSize to work properly
48844                 // so that unallocated space caused by maxWidth being less than flexed width
48845                 // can be reallocated to subsequent flexed boxes.
48846                 Ext.Array.sort(flexedBoxes, me.flexSortFn);
48847
48848                 // Calculate the size of each flexed item, and attempt to set it.
48849                 for (i = 0; i < flexedBoxes.length; i++) {
48850                     calcs = flexedBoxes[i];
48851                     child = calcs.component;
48852                     childMargins = calcs.margins;
48853
48854                     flexedSize = math.ceil((child.flex / remainingFlex) * remainingSpace);
48855
48856                     // Implement maxSize and minSize check
48857                     flexedSize = Math.max(child['min' + parallelPrefixCap] || 0, math.min(child['max' + parallelPrefixCap] || infiniteValue, flexedSize));
48858
48859                     // Remaining space has already had all parallel margins subtracted from it, so just subtract consumed size
48860                     remainingSpace -= flexedSize;
48861                     remainingFlex -= child.flex;
48862
48863                     calcs.dirtySize = calcs.dirtySize || calcs[parallelPrefix] != flexedSize;
48864                     calcs[parallelPrefix] = flexedSize;
48865                 }
48866             }
48867         }
48868
48869         if (isCenter) {
48870             parallelOffset += availableSpace / 2;
48871         }
48872         else if (isEnd) {
48873             parallelOffset += availableSpace;
48874         }
48875
48876         // Fix for left and right docked Components in a dock component layout. This is for docked Headers and docked Toolbars.
48877         // Older Microsoft browsers do not size a position:absolute element's width to match its content.
48878         // So in this case, in the updateInnerCtSize method we may need to adjust the size of the owning Container's element explicitly based upon
48879         // the discovered max width. So here we put a calculatedWidth property in the metadata to facilitate this.
48880         if (me.owner.dock && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) && !me.owner.width && me.direction == 'vertical') {
48881
48882             calculatedWidth = maxSize + me.owner.el.getPadding('lr') + me.owner.el.getBorderWidth('lr');
48883             if (me.owner.frameSize) {
48884                 calculatedWidth += me.owner.frameSize.left + me.owner.frameSize.right;
48885             }
48886             // If the owning element is not sized, calculate the available width to center or stretch in based upon maxSize
48887             availPerpendicularSize = Math.min(availPerpendicularSize, targetSize.width = maxSize + padding.left + padding.right);
48888         }
48889
48890         //finally, calculate the left and top position of each item
48891         for (i = 0; i < visibleCount; i++) {
48892             child = visibleItems[i];
48893             calcs = boxes[i];
48894
48895             childMargins = calcs.margins;
48896
48897             perpendicularMargins = childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom];
48898
48899             // Advance past the "before" margin
48900             parallelOffset += childMargins[me.parallelBefore];
48901
48902             calcs[me.parallelBefore] = parallelOffset;
48903             calcs[me.perpendicularLeftTop] = perpendicularOffset + childMargins[me.perpendicularLeftTop];
48904
48905             if (me.align == 'stretch') {
48906                 stretchSize = constrain(availPerpendicularSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
48907                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
48908                 calcs[perpendicularPrefix] = stretchSize;
48909             }
48910             else if (me.align == 'stretchmax') {
48911                 stretchSize = constrain(maxSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
48912                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
48913                 calcs[perpendicularPrefix] = stretchSize;
48914             }
48915             else if (me.align == me.alignCenteringString) {
48916                 // When calculating a centered position within the content box of the innerCt, the width of the borders must be subtracted from
48917                 // the size to yield the space available to center within.
48918                 // The updateInnerCtSize method explicitly adds the border widths to the set size of the innerCt.
48919                 diff = mmax(availPerpendicularSize, maxSize) - innerCtBorderWidth - calcs[perpendicularPrefix];
48920                 if (diff > 0) {
48921                     calcs[me.perpendicularLeftTop] = perpendicularOffset + Math.round(diff / 2);
48922                 }
48923             }
48924
48925             // Advance past the box size and the "after" margin
48926             parallelOffset += (calcs[parallelPrefix] || 0) + childMargins[me.parallelAfter];
48927         }
48928
48929         return {
48930             boxes: boxes,
48931             meta : {
48932                 calculatedWidth: calculatedWidth,
48933                 maxSize: maxSize,
48934                 nonFlexSize: nonFlexSize,
48935                 desiredSize: desiredSize,
48936                 minimumSize: minimumSize,
48937                 shortfall: shortfall,
48938                 tooNarrow: tooNarrow
48939             }
48940         };
48941     },
48942
48943     onRemove: function(comp){
48944         this.callParent(arguments);
48945         if (this.overflowHandler) {
48946             this.overflowHandler.onRemove(comp);
48947         }
48948     },
48949
48950     /**
48951      * @private
48952      */
48953     initOverflowHandler: function() {
48954         var handler = this.overflowHandler;
48955
48956         if (typeof handler == 'string') {
48957             handler = {
48958                 type: handler
48959             };
48960         }
48961
48962         var handlerType = 'None';
48963         if (handler && handler.type !== undefined) {
48964             handlerType = handler.type;
48965         }
48966
48967         var constructor = Ext.layout.container.boxOverflow[handlerType];
48968         if (constructor[this.type]) {
48969             constructor = constructor[this.type];
48970         }
48971
48972         this.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.' + handlerType, this, handler);
48973     },
48974
48975     /**
48976      * @private
48977      * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
48978      * when laying out
48979      */
48980     onLayout: function() {
48981         this.callParent();
48982         // Clear the innerCt size so it doesn't influence the child items.
48983         if (this.clearInnerCtOnLayout === true && this.adjustmentPass !== true) {
48984             this.innerCt.setSize(null, null);
48985         }
48986
48987         var me = this,
48988             targetSize = me.getLayoutTargetSize(),
48989             items = me.getVisibleItems(),
48990             calcs = me.calculateChildBoxes(items, targetSize),
48991             boxes = calcs.boxes,
48992             meta = calcs.meta,
48993             handler, method, results;
48994
48995         if (me.autoSize && calcs.meta.desiredSize) {
48996             targetSize[me.parallelPrefix] = calcs.meta.desiredSize;
48997         }
48998
48999         //invoke the overflow handler, if one is configured
49000         if (meta.shortfall > 0) {
49001             handler = me.overflowHandler;
49002             method = meta.tooNarrow ? 'handleOverflow': 'clearOverflow';
49003
49004             results = handler[method](calcs, targetSize);
49005
49006             if (results) {
49007                 if (results.targetSize) {
49008                     targetSize = results.targetSize;
49009                 }
49010
49011                 if (results.recalculate) {
49012                     items = me.getVisibleItems();
49013                     calcs = me.calculateChildBoxes(items, targetSize);
49014                     boxes = calcs.boxes;
49015                 }
49016             }
49017         } else {
49018             me.overflowHandler.clearOverflow();
49019         }
49020
49021         /**
49022          * @private
49023          * @property layoutTargetLastSize
49024          * @type Object
49025          * Private cache of the last measured size of the layout target. This should never be used except by
49026          * BoxLayout subclasses during their onLayout run.
49027          */
49028         me.layoutTargetLastSize = targetSize;
49029
49030         /**
49031          * @private
49032          * @property childBoxCache
49033          * @type Array
49034          * Array of the last calculated height, width, top and left positions of each visible rendered component
49035          * within the Box layout.
49036          */
49037         me.childBoxCache = calcs;
49038
49039         me.updateInnerCtSize(targetSize, calcs);
49040         me.updateChildBoxes(boxes);
49041         me.handleTargetOverflow(targetSize);
49042     },
49043     
49044     animCallback: Ext.emptyFn,
49045
49046     /**
49047      * Resizes and repositions each child component
49048      * @param {Object[]} boxes The box measurements
49049      */
49050     updateChildBoxes: function(boxes) {
49051         var me = this,
49052             i = 0,
49053             length = boxes.length,
49054             animQueue = [],
49055             dd = Ext.dd.DDM.getDDById(me.innerCt.id), // Any DD active on this layout's element (The BoxReorderer plugin does this.)
49056             oldBox, newBox, changed, comp, boxAnim, animCallback;
49057
49058         for (; i < length; i++) {
49059             newBox = boxes[i];
49060             comp = newBox.component;
49061
49062             // If a Component is being drag/dropped, skip positioning it.
49063             // Accomodate the BoxReorderer plugin: Its current dragEl must not be positioned by the layout
49064             if (dd && (dd.getDragEl() === comp.el.dom)) {
49065                 continue;
49066             }
49067
49068             changed = false;
49069
49070             oldBox = me.getChildBox(comp);
49071
49072             // If we are animating, we build up an array of Anim config objects, one for each
49073             // child Component which has any changed box properties. Those with unchanged
49074             // properties are not animated.
49075             if (me.animate) {
49076                 // Animate may be a config object containing callback.
49077                 animCallback = me.animate.callback || me.animate;
49078                 boxAnim = {
49079                     layoutAnimation: true,  // Component Target handler must use set*Calculated*Size
49080                     target: comp,
49081                     from: {},
49082                     to: {},
49083                     listeners: {}
49084                 };
49085                 // Only set from and to properties when there's a change.
49086                 // Perform as few Component setter methods as possible.
49087                 // Temporarily set the property values that we are not animating
49088                 // so that doComponentLayout does not auto-size them.
49089                 if (!isNaN(newBox.width) && (newBox.width != oldBox.width)) {
49090                     changed = true;
49091                     // boxAnim.from.width = oldBox.width;
49092                     boxAnim.to.width = newBox.width;
49093                 }
49094                 if (!isNaN(newBox.height) && (newBox.height != oldBox.height)) {
49095                     changed = true;
49096                     // boxAnim.from.height = oldBox.height;
49097                     boxAnim.to.height = newBox.height;
49098                 }
49099                 if (!isNaN(newBox.left) && (newBox.left != oldBox.left)) {
49100                     changed = true;
49101                     // boxAnim.from.left = oldBox.left;
49102                     boxAnim.to.left = newBox.left;
49103                 }
49104                 if (!isNaN(newBox.top) && (newBox.top != oldBox.top)) {
49105                     changed = true;
49106                     // boxAnim.from.top = oldBox.top;
49107                     boxAnim.to.top = newBox.top;
49108                 }
49109                 if (changed) {
49110                     animQueue.push(boxAnim);
49111                 }
49112             } else {
49113                 if (newBox.dirtySize) {
49114                     if (newBox.width !== oldBox.width || newBox.height !== oldBox.height) {
49115                         me.setItemSize(comp, newBox.width, newBox.height);
49116                     }
49117                 }
49118                 // Don't set positions to NaN
49119                 if (isNaN(newBox.left) || isNaN(newBox.top)) {
49120                     continue;
49121                 }
49122                 comp.setPosition(newBox.left, newBox.top);
49123             }
49124         }
49125
49126         // Kick off any queued animations
49127         length = animQueue.length;
49128         if (length) {
49129
49130             // A function which cleans up when a Component's animation is done.
49131             // The last one to finish calls the callback.
49132             var afterAnimate = function(anim) {
49133                 // When we've animated all changed boxes into position, clear our busy flag and call the callback.
49134                 length -= 1;
49135                 if (!length) {
49136                     me.animCallback(anim);
49137                     me.layoutBusy = false;
49138                     if (Ext.isFunction(animCallback)) {
49139                         animCallback();
49140                     }
49141                 }
49142             };
49143
49144             var beforeAnimate = function() {
49145                 me.layoutBusy = true;
49146             };
49147
49148             // Start each box animation off
49149             for (i = 0, length = animQueue.length; i < length; i++) {
49150                 boxAnim = animQueue[i];
49151
49152                 // Clean up the Component after. Clean up the *layout* after the last animation finishes
49153                 boxAnim.listeners.afteranimate = afterAnimate;
49154
49155                 // The layout is busy during animation, and may not be called, so set the flag when the first animation begins
49156                 if (!i) {
49157                     boxAnim.listeners.beforeanimate = beforeAnimate;
49158                 }
49159                 if (me.animate.duration) {
49160                     boxAnim.duration = me.animate.duration;
49161                 }
49162                 comp = boxAnim.target;
49163                 delete boxAnim.target;
49164                 // Stop any currently running animation
49165                 comp.stopAnimation();
49166                 comp.animate(boxAnim);
49167             }
49168         }
49169     },
49170
49171     /**
49172      * @private
49173      * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
49174      * to make sure all child items fit within it. We call this before sizing the children because if our child
49175      * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
49176      * again immediately afterwards, giving a performance hit.
49177      * Subclasses should provide an implementation.
49178      * @param {Object} currentSize The current height and width of the innerCt
49179      * @param {Object} calculations The new box calculations of all items to be laid out
49180      */
49181     updateInnerCtSize: function(tSize, calcs) {
49182         var me = this,
49183             mmax = Math.max,
49184             align = me.align,
49185             padding = me.padding,
49186             width = tSize.width,
49187             height = tSize.height,
49188             meta = calcs.meta,
49189             innerCtWidth,
49190             innerCtHeight;
49191
49192         if (me.direction == 'horizontal') {
49193             innerCtWidth = width;
49194             innerCtHeight = meta.maxSize + padding.top + padding.bottom + me.innerCt.getBorderWidth('tb');
49195
49196             if (align == 'stretch') {
49197                 innerCtHeight = height;
49198             }
49199             else if (align == 'middle') {
49200                 innerCtHeight = mmax(height, innerCtHeight);
49201             }
49202         } else {
49203             innerCtHeight = height;
49204             innerCtWidth = meta.maxSize + padding.left + padding.right + me.innerCt.getBorderWidth('lr');
49205
49206             if (align == 'stretch') {
49207                 innerCtWidth = width;
49208             }
49209             else if (align == 'center') {
49210                 innerCtWidth = mmax(width, innerCtWidth);
49211             }
49212         }
49213         me.getRenderTarget().setSize(innerCtWidth || undefined, innerCtHeight || undefined);
49214
49215         // If a calculated width has been found (and this only happens for auto-width vertical docked Components in old Microsoft browsers)
49216         // then, if the Component has not assumed the size of its content, set it to do so.
49217         if (meta.calculatedWidth && me.owner.el.getWidth() > meta.calculatedWidth) {
49218             me.owner.el.setWidth(meta.calculatedWidth);
49219         }
49220
49221         if (me.innerCt.dom.scrollTop) {
49222             me.innerCt.dom.scrollTop = 0;
49223         }
49224     },
49225
49226     /**
49227      * @private
49228      * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
49229      * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
49230      * target. Having a Box layout inside such a target is therefore not recommended.
49231      * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
49232      * @param {Ext.container.Container} container The container
49233      * @param {Ext.Element} target The target element
49234      * @return True if the layout overflowed, and was reflowed in a secondary onLayout call.
49235      */
49236     handleTargetOverflow: function(previousTargetSize) {
49237         var target = this.getTarget(),
49238             overflow = target.getStyle('overflow'),
49239             newTargetSize;
49240
49241         if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
49242             newTargetSize = this.getLayoutTargetSize();
49243             if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height) {
49244                 this.adjustmentPass = true;
49245                 this.onLayout();
49246                 return true;
49247             }
49248         }
49249
49250         delete this.adjustmentPass;
49251     },
49252
49253     // private
49254     isValidParent : function(item, target, position) {
49255         // Note: Box layouts do not care about order within the innerCt element because it's an absolutely positioning layout
49256         // We only care whether the item is a direct child of the innerCt element.
49257         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
49258         return (itemEl && this.innerCt && itemEl.parentNode === this.innerCt.dom) || false;
49259     },
49260
49261     // Overridden method from AbstractContainer.
49262     // Used in the base AbstractLayout.beforeLayout method to render all items into.
49263     getRenderTarget: function() {
49264         if (!this.innerCt) {
49265             // the innerCt prevents wrapping and shuffling while the container is resizing
49266             this.innerCt = this.getTarget().createChild({
49267                 cls: this.innerCls,
49268                 role: 'presentation'
49269             });
49270             this.padding = Ext.util.Format.parseBox(this.padding);
49271         }
49272         return this.innerCt;
49273     },
49274
49275     // private
49276     renderItem: function(item, target) {
49277         this.callParent(arguments);
49278         var me = this,
49279             itemEl = item.getEl(),
49280             style = itemEl.dom.style,
49281             margins = item.margins || item.margin;
49282
49283         // Parse the item's margin/margins specification
49284         if (margins) {
49285             if (Ext.isString(margins) || Ext.isNumber(margins)) {
49286                 margins = Ext.util.Format.parseBox(margins);
49287             } else {
49288                 Ext.applyIf(margins, {top: 0, right: 0, bottom: 0, left: 0});
49289             }
49290         } else {
49291             margins = Ext.apply({}, me.defaultMargins);
49292         }
49293
49294         // Add any before/after CSS margins to the configured margins, and zero the CSS margins
49295         margins.top    += itemEl.getMargin('t');
49296         margins.right  += itemEl.getMargin('r');
49297         margins.bottom += itemEl.getMargin('b');
49298         margins.left   += itemEl.getMargin('l');
49299         margins.height  = margins.top  + margins.bottom;
49300         margins.width   = margins.left + margins.right;
49301         style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = '0';
49302
49303         // Item must reference calculated margins.
49304         item.margins = margins;
49305     },
49306
49307     /**
49308      * @private
49309      */
49310     destroy: function() {
49311         Ext.destroy(this.innerCt, this.overflowHandler);
49312         this.callParent(arguments);
49313     }
49314 });
49315 /**
49316  * A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
49317  * space between child items containing a numeric `flex` configuration.
49318  *
49319  * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
49320  *
49321  *     @example
49322  *     Ext.create('Ext.Panel', {
49323  *         width: 500,
49324  *         height: 300,
49325  *         title: "HBoxLayout Panel",
49326  *         layout: {
49327  *             type: 'hbox',
49328  *             align: 'stretch'
49329  *         },
49330  *         renderTo: document.body,
49331  *         items: [{
49332  *             xtype: 'panel',
49333  *             title: 'Inner Panel One',
49334  *             flex: 2
49335  *         },{
49336  *             xtype: 'panel',
49337  *             title: 'Inner Panel Two',
49338  *             flex: 1
49339  *         },{
49340  *             xtype: 'panel',
49341  *             title: 'Inner Panel Three',
49342  *             flex: 1
49343  *         }]
49344  *     });
49345  */
49346 Ext.define('Ext.layout.container.HBox', {
49347
49348     /* Begin Definitions */
49349
49350     alias: ['layout.hbox'],
49351     extend: 'Ext.layout.container.Box',
49352     alternateClassName: 'Ext.layout.HBoxLayout',
49353
49354     /* End Definitions */
49355
49356     /**
49357      * @cfg {String} align
49358      * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
49359      *
49360      * - **top** : **Default** child items are aligned vertically at the **top** of the container
49361      * - **middle** : child items are aligned vertically in the **middle** of the container
49362      * - **stretch** : child items are stretched vertically to fill the height of the container
49363      * - **stretchmax** : child items are stretched vertically to the height of the largest item.
49364      */
49365     align: 'top', // top, middle, stretch, strechmax
49366
49367     //@private
49368     alignCenteringString: 'middle',
49369
49370     type : 'hbox',
49371
49372     direction: 'horizontal',
49373
49374     // When creating an argument list to setSize, use this order
49375     parallelSizeIndex: 0,
49376     perpendicularSizeIndex: 1,
49377
49378     parallelPrefix: 'width',
49379     parallelPrefixCap: 'Width',
49380     parallelLT: 'l',
49381     parallelRB: 'r',
49382     parallelBefore: 'left',
49383     parallelBeforeCap: 'Left',
49384     parallelAfter: 'right',
49385     parallelPosition: 'x',
49386
49387     perpendicularPrefix: 'height',
49388     perpendicularPrefixCap: 'Height',
49389     perpendicularLT: 't',
49390     perpendicularRB: 'b',
49391     perpendicularLeftTop: 'top',
49392     perpendicularRightBottom: 'bottom',
49393     perpendicularPosition: 'y',
49394     configureItem: function(item) {
49395         if (item.flex) {
49396             item.layoutManagedWidth = 1;
49397         } else {
49398             item.layoutManagedWidth = 2;
49399         }
49400
49401         if (this.align === 'stretch' || this.align === 'stretchmax') {
49402             item.layoutManagedHeight = 1;
49403         } else {
49404             item.layoutManagedHeight = 2;
49405         }
49406         this.callParent(arguments);
49407     }
49408 });
49409 /**
49410  * A layout that arranges items vertically down a Container. This layout optionally divides available vertical space
49411  * between child items containing a numeric `flex` configuration.
49412  *
49413  * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
49414  *
49415  *     @example
49416  *     Ext.create('Ext.Panel', {
49417  *         width: 500,
49418  *         height: 400,
49419  *         title: "VBoxLayout Panel",
49420  *         layout: {
49421  *             type: 'vbox',
49422  *             align: 'center'
49423  *         },
49424  *         renderTo: document.body,
49425  *         items: [{
49426  *             xtype: 'panel',
49427  *             title: 'Inner Panel One',
49428  *             width: 250,
49429  *             flex: 2
49430  *         },
49431  *         {
49432  *             xtype: 'panel',
49433  *             title: 'Inner Panel Two',
49434  *             width: 250,
49435  *             flex: 4
49436  *         },
49437  *         {
49438  *             xtype: 'panel',
49439  *             title: 'Inner Panel Three',
49440  *             width: '50%',
49441  *             flex: 4
49442  *         }]
49443  *     });
49444  */
49445 Ext.define('Ext.layout.container.VBox', {
49446
49447     /* Begin Definitions */
49448
49449     alias: ['layout.vbox'],
49450     extend: 'Ext.layout.container.Box',
49451     alternateClassName: 'Ext.layout.VBoxLayout',
49452
49453     /* End Definitions */
49454
49455     /**
49456      * @cfg {String} align
49457      * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
49458      *
49459      * - **left** : **Default** child items are aligned horizontally at the **left** side of the container
49460      * - **center** : child items are aligned horizontally at the **mid-width** of the container
49461      * - **stretch** : child items are stretched horizontally to fill the width of the container
49462      * - **stretchmax** : child items are stretched horizontally to the size of the largest item.
49463      */
49464     align : 'left', // left, center, stretch, strechmax
49465
49466     //@private
49467     alignCenteringString: 'center',
49468
49469     type: 'vbox',
49470
49471     direction: 'vertical',
49472
49473     // When creating an argument list to setSize, use this order
49474     parallelSizeIndex: 1,
49475     perpendicularSizeIndex: 0,
49476
49477     parallelPrefix: 'height',
49478     parallelPrefixCap: 'Height',
49479     parallelLT: 't',
49480     parallelRB: 'b',
49481     parallelBefore: 'top',
49482     parallelBeforeCap: 'Top',
49483     parallelAfter: 'bottom',
49484     parallelPosition: 'y',
49485
49486     perpendicularPrefix: 'width',
49487     perpendicularPrefixCap: 'Width',
49488     perpendicularLT: 'l',
49489     perpendicularRB: 'r',
49490     perpendicularLeftTop: 'left',
49491     perpendicularRightBottom: 'right',
49492     perpendicularPosition: 'x',
49493     configureItem: function(item) {
49494         if (item.flex) {
49495             item.layoutManagedHeight = 1;
49496         } else {
49497             item.layoutManagedHeight = 2;
49498         }
49499
49500         if (this.align === 'stretch' || this.align === 'stretchmax') {
49501             item.layoutManagedWidth = 1;
49502         } else {
49503             item.layoutManagedWidth = 2;
49504         }
49505         this.callParent(arguments);
49506     }
49507 });
49508 /**
49509  * @class Ext.FocusManager
49510
49511 The FocusManager is responsible for globally:
49512
49513 1. Managing component focus
49514 2. Providing basic keyboard navigation
49515 3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
49516
49517 To activate the FocusManager, simply call `Ext.FocusManager.enable();`. In turn, you may
49518 deactivate the FocusManager by subsequently calling `Ext.FocusManager.disable();.  The
49519 FocusManager is disabled by default.
49520
49521 To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
49522
49523 Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
49524 that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
49525 call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
49526 {@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
49527
49528  * @singleton
49529  * @author Jarred Nicholls <jarred@sencha.com>
49530  * @docauthor Jarred Nicholls <jarred@sencha.com>
49531  */
49532 Ext.define('Ext.FocusManager', {
49533     singleton: true,
49534     alternateClassName: 'Ext.FocusMgr',
49535
49536     mixins: {
49537         observable: 'Ext.util.Observable'
49538     },
49539
49540     requires: [
49541         'Ext.ComponentManager',
49542         'Ext.ComponentQuery',
49543         'Ext.util.HashMap',
49544         'Ext.util.KeyNav'
49545     ],
49546
49547     /**
49548      * @property {Boolean} enabled
49549      * Whether or not the FocusManager is currently enabled
49550      */
49551     enabled: false,
49552
49553     /**
49554      * @property {Ext.Component} focusedCmp
49555      * The currently focused component. Defaults to `undefined`.
49556      */
49557
49558     focusElementCls: Ext.baseCSSPrefix + 'focus-element',
49559
49560     focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
49561
49562     /**
49563      * @property {String[]} whitelist
49564      * A list of xtypes that should ignore certain navigation input keys and
49565      * allow for the default browser event/behavior. These input keys include:
49566      *
49567      * 1. Backspace
49568      * 2. Delete
49569      * 3. Left
49570      * 4. Right
49571      * 5. Up
49572      * 6. Down
49573      *
49574      * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
49575      * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
49576      * the user to move the input cursor left and right, and to delete characters, etc.
49577      */
49578     whitelist: [
49579         'textfield'
49580     ],
49581
49582     tabIndexWhitelist: [
49583         'a',
49584         'button',
49585         'embed',
49586         'frame',
49587         'iframe',
49588         'img',
49589         'input',
49590         'object',
49591         'select',
49592         'textarea'
49593     ],
49594
49595     constructor: function() {
49596         var me = this,
49597             CQ = Ext.ComponentQuery;
49598
49599         me.addEvents(
49600             /**
49601              * @event beforecomponentfocus
49602              * Fires before a component becomes focused. Return `false` to prevent
49603              * the component from gaining focus.
49604              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
49605              * @param {Ext.Component} cmp The component that is being focused
49606              * @param {Ext.Component} previousCmp The component that was previously focused,
49607              * or `undefined` if there was no previously focused component.
49608              */
49609             'beforecomponentfocus',
49610
49611             /**
49612              * @event componentfocus
49613              * Fires after a component becomes focused.
49614              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
49615              * @param {Ext.Component} cmp The component that has been focused
49616              * @param {Ext.Component} previousCmp The component that was previously focused,
49617              * or `undefined` if there was no previously focused component.
49618              */
49619             'componentfocus',
49620
49621             /**
49622              * @event disable
49623              * Fires when the FocusManager is disabled
49624              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
49625              */
49626             'disable',
49627
49628             /**
49629              * @event enable
49630              * Fires when the FocusManager is enabled
49631              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
49632              */
49633             'enable'
49634         );
49635
49636         // Setup KeyNav that's bound to document to catch all
49637         // unhandled/bubbled key events for navigation
49638         me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
49639             disabled: true,
49640             scope: me,
49641
49642             backspace: me.focusLast,
49643             enter: me.navigateIn,
49644             esc: me.navigateOut,
49645             tab: me.navigateSiblings
49646
49647             //space: me.navigateIn,
49648             //del: me.focusLast,
49649             //left: me.navigateSiblings,
49650             //right: me.navigateSiblings,
49651             //down: me.navigateSiblings,
49652             //up: me.navigateSiblings
49653         });
49654
49655         me.focusData = {};
49656         me.subscribers = Ext.create('Ext.util.HashMap');
49657         me.focusChain = {};
49658
49659         // Setup some ComponentQuery pseudos
49660         Ext.apply(CQ.pseudos, {
49661             focusable: function(cmps) {
49662                 var len = cmps.length,
49663                     results = [],
49664                     i = 0,
49665                     c,
49666
49667                     isFocusable = function(x) {
49668                         return x && x.focusable !== false && CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el && c.el.dom && c.el.isVisible()}');
49669                     };
49670
49671                 for (; i < len; i++) {
49672                     c = cmps[i];
49673                     if (isFocusable(c)) {
49674                         results.push(c);
49675                     }
49676                 }
49677
49678                 return results;
49679             },
49680
49681             nextFocus: function(cmps, idx, step) {
49682                 step = step || 1;
49683                 idx = parseInt(idx, 10);
49684
49685                 var len = cmps.length,
49686                     i = idx + step,
49687                     c;
49688
49689                 for (; i != idx; i += step) {
49690                     if (i >= len) {
49691                         i = 0;
49692                     } else if (i < 0) {
49693                         i = len - 1;
49694                     }
49695
49696                     c = cmps[i];
49697                     if (CQ.is(c, ':focusable')) {
49698                         return [c];
49699                     } else if (c.placeholder && CQ.is(c.placeholder, ':focusable')) {
49700                         return [c.placeholder];
49701                     }
49702                 }
49703
49704                 return [];
49705             },
49706
49707             prevFocus: function(cmps, idx) {
49708                 return this.nextFocus(cmps, idx, -1);
49709             },
49710
49711             root: function(cmps) {
49712                 var len = cmps.length,
49713                     results = [],
49714                     i = 0,
49715                     c;
49716
49717                 for (; i < len; i++) {
49718                     c = cmps[i];
49719                     if (!c.ownerCt) {
49720                         results.push(c);
49721                     }
49722                 }
49723
49724                 return results;
49725             }
49726         });
49727     },
49728
49729     /**
49730      * Adds the specified xtype to the {@link #whitelist}.
49731      * @param {String/String[]} xtype Adds the xtype(s) to the {@link #whitelist}.
49732      */
49733     addXTypeToWhitelist: function(xtype) {
49734         var me = this;
49735
49736         if (Ext.isArray(xtype)) {
49737             Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
49738             return;
49739         }
49740
49741         if (!Ext.Array.contains(me.whitelist, xtype)) {
49742             me.whitelist.push(xtype);
49743         }
49744     },
49745
49746     clearComponent: function(cmp) {
49747         clearTimeout(this.cmpFocusDelay);
49748         if (!cmp.isDestroyed) {
49749             cmp.blur();
49750         }
49751     },
49752
49753     /**
49754      * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
49755      */
49756     disable: function() {
49757         var me = this;
49758
49759         if (!me.enabled) {
49760             return;
49761         }
49762
49763         delete me.options;
49764         me.enabled = false;
49765
49766         Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
49767
49768         me.removeDOM();
49769
49770         // Stop handling key navigation
49771         me.keyNav.disable();
49772
49773         // disable focus for all components
49774         me.setFocusAll(false);
49775
49776         me.fireEvent('disable', me);
49777     },
49778
49779     /**
49780      * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
49781      * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
49782         - focusFrame : Boolean
49783             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
49784      * @markdown
49785      */
49786     enable: function(options) {
49787         var me = this;
49788
49789         if (options === true) {
49790             options = { focusFrame: true };
49791         }
49792         me.options = options = options || {};
49793
49794         if (me.enabled) {
49795             return;
49796         }
49797
49798         // Handle components that are newly added after we are enabled
49799         Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
49800
49801         me.initDOM(options);
49802
49803         // Start handling key navigation
49804         me.keyNav.enable();
49805
49806         // enable focus for all components
49807         me.setFocusAll(true, options);
49808
49809         // Finally, let's focus our global focus el so we start fresh
49810         me.focusEl.focus();
49811         delete me.focusedCmp;
49812
49813         me.enabled = true;
49814         me.fireEvent('enable', me);
49815     },
49816
49817     focusLast: function(e) {
49818         var me = this;
49819
49820         if (me.isWhitelisted(me.focusedCmp)) {
49821             return true;
49822         }
49823
49824         // Go back to last focused item
49825         if (me.previousFocusedCmp) {
49826             me.previousFocusedCmp.focus();
49827         }
49828     },
49829
49830     getRootComponents: function() {
49831         var me = this,
49832             CQ = Ext.ComponentQuery,
49833             inline = CQ.query(':focusable:root:not([floating])'),
49834             floating = CQ.query(':focusable:root[floating]');
49835
49836         // Floating items should go to the top of our root stack, and be ordered
49837         // by their z-index (highest first)
49838         floating.sort(function(a, b) {
49839             return a.el.getZIndex() > b.el.getZIndex();
49840         });
49841
49842         return floating.concat(inline);
49843     },
49844
49845     initDOM: function(options) {
49846         var me = this,
49847             sp = '&#160',
49848             cls = me.focusFrameCls;
49849
49850         if (!Ext.isReady) {
49851             Ext.onReady(me.initDOM, me);
49852             return;
49853         }
49854
49855         // Create global focus element
49856         if (!me.focusEl) {
49857             me.focusEl = Ext.getBody().createChild({
49858                 tabIndex: '-1',
49859                 cls: me.focusElementCls,
49860                 html: sp
49861             });
49862         }
49863
49864         // Create global focus frame
49865         if (!me.focusFrame && options.focusFrame) {
49866             me.focusFrame = Ext.getBody().createChild({
49867                 cls: cls,
49868                 children: [
49869                     { cls: cls + '-top' },
49870                     { cls: cls + '-bottom' },
49871                     { cls: cls + '-left' },
49872                     { cls: cls + '-right' }
49873                 ],
49874                 style: 'top: -100px; left: -100px;'
49875             });
49876             me.focusFrame.setVisibilityMode(Ext.Element.DISPLAY);
49877             me.focusFrameWidth = 2;
49878             me.focusFrame.hide().setLeftTop(0, 0);
49879         }
49880     },
49881
49882     isWhitelisted: function(cmp) {
49883         return cmp && Ext.Array.some(this.whitelist, function(x) {
49884             return cmp.isXType(x);
49885         });
49886     },
49887
49888     navigateIn: function(e) {
49889         var me = this,
49890             focusedCmp = me.focusedCmp,
49891             rootCmps,
49892             firstChild;
49893
49894         if (!focusedCmp) {
49895             // No focus yet, so focus the first root cmp on the page
49896             rootCmps = me.getRootComponents();
49897             if (rootCmps.length) {
49898                 rootCmps[0].focus();
49899             }
49900         } else {
49901             // Drill into child ref items of the focused cmp, if applicable.
49902             // This works for any Component with a getRefItems implementation.
49903             firstChild = Ext.ComponentQuery.query('>:focusable', focusedCmp)[0];
49904             if (firstChild) {
49905                 firstChild.focus();
49906             } else {
49907                 // Let's try to fire a click event, as if it came from the mouse
49908                 if (Ext.isFunction(focusedCmp.onClick)) {
49909                     e.button = 0;
49910                     focusedCmp.onClick(e);
49911                     focusedCmp.focus();
49912                 }
49913             }
49914         }
49915     },
49916
49917     navigateOut: function(e) {
49918         var me = this,
49919             parent;
49920
49921         if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
49922             me.focusEl.focus();
49923         } else {
49924             parent.focus();
49925         }
49926
49927         // In some browsers (Chrome) FocusManager can handle this before other
49928         // handlers. Ext Windows have their own Esc key handling, so we need to
49929         // return true here to allow the event to bubble.
49930         return true;
49931     },
49932
49933     navigateSiblings: function(e, source, parent) {
49934         var me = this,
49935             src = source || me,
49936             key = e.getKey(),
49937             EO = Ext.EventObject,
49938             goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
49939             checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
49940             nextSelector = goBack ? 'prev' : 'next',
49941             idx, next, focusedCmp;
49942
49943         focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
49944         if (!focusedCmp && !parent) {
49945             return;
49946         }
49947
49948         if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
49949             return true;
49950         }
49951
49952         parent = parent || focusedCmp.up();
49953         if (parent) {
49954             idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
49955             next = Ext.ComponentQuery.query('>:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
49956             if (next && focusedCmp !== next) {
49957                 next.focus();
49958                 return next;
49959             }
49960         }
49961     },
49962
49963     onComponentBlur: function(cmp, e) {
49964         var me = this;
49965
49966         if (me.focusedCmp === cmp) {
49967             me.previousFocusedCmp = cmp;
49968             delete me.focusedCmp;
49969         }
49970
49971         if (me.focusFrame) {
49972             me.focusFrame.hide();
49973         }
49974     },
49975
49976     onComponentCreated: function(hash, id, cmp) {
49977         this.setFocus(cmp, true, this.options);
49978     },
49979
49980     onComponentDestroy: function(cmp) {
49981         this.setFocus(cmp, false);
49982     },
49983
49984     onComponentFocus: function(cmp, e) {
49985         var me = this,
49986             chain = me.focusChain;
49987
49988         if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
49989             me.clearComponent(cmp);
49990
49991             // Check our focus chain, so we don't run into a never ending recursion
49992             // If we've attempted (unsuccessfully) to focus this component before,
49993             // then we're caught in a loop of child->parent->...->child and we
49994             // need to cut the loop off rather than feed into it.
49995             if (chain[cmp.id]) {
49996                 return;
49997             }
49998
49999             // Try to focus the parent instead
50000             var parent = cmp.up();
50001             if (parent) {
50002                 // Add component to our focus chain to detect infinite focus loop
50003                 // before we fire off an attempt to focus our parent.
50004                 // See the comments above.
50005                 chain[cmp.id] = true;
50006                 parent.focus();
50007             }
50008
50009             return;
50010         }
50011
50012         // Clear our focus chain when we have a focusable component
50013         me.focusChain = {};
50014
50015         // Defer focusing for 90ms so components can do a layout/positioning
50016         // and give us an ability to buffer focuses
50017         clearTimeout(me.cmpFocusDelay);
50018         if (arguments.length !== 2) {
50019             me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
50020             return;
50021         }
50022
50023         if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
50024             me.clearComponent(cmp);
50025             return;
50026         }
50027
50028         me.focusedCmp = cmp;
50029
50030         // If we have a focus frame, show it around the focused component
50031         if (me.shouldShowFocusFrame(cmp)) {
50032             var cls = '.' + me.focusFrameCls + '-',
50033                 ff = me.focusFrame,
50034                 fw = me.focusFrameWidth,
50035                 box = cmp.el.getPageBox(),
50036
50037             // Size the focus frame's t/b/l/r according to the box
50038             // This leaves a hole in the middle of the frame so user
50039             // interaction w/ the mouse can continue
50040                 bt = box.top,
50041                 bl = box.left,
50042                 bw = box.width,
50043                 bh = box.height,
50044                 ft = ff.child(cls + 'top'),
50045                 fb = ff.child(cls + 'bottom'),
50046                 fl = ff.child(cls + 'left'),
50047                 fr = ff.child(cls + 'right');
50048
50049             ft.setWidth(bw).setLeftTop(bl, bt);
50050             fb.setWidth(bw).setLeftTop(bl, bt + bh - fw);
50051             fl.setHeight(bh - fw - fw).setLeftTop(bl, bt + fw);
50052             fr.setHeight(bh - fw - fw).setLeftTop(bl + bw - fw, bt + fw);
50053
50054             ff.show();
50055         }
50056
50057         me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
50058     },
50059
50060     onComponentHide: function(cmp) {
50061         var me = this,
50062             CQ = Ext.ComponentQuery,
50063             cmpHadFocus = false,
50064             focusedCmp,
50065             parent;
50066
50067         if (me.focusedCmp) {
50068             focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
50069             cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
50070
50071             if (focusedCmp) {
50072                 me.clearComponent(focusedCmp);
50073             }
50074         }
50075
50076         me.clearComponent(cmp);
50077
50078         if (cmpHadFocus) {
50079             parent = CQ.query('^:focusable', cmp)[0];
50080             if (parent) {
50081                 parent.focus();
50082             }
50083         }
50084     },
50085
50086     removeDOM: function() {
50087         var me = this;
50088
50089         // If we are still enabled globally, or there are still subscribers
50090         // then we will halt here, since our DOM stuff is still being used
50091         if (me.enabled || me.subscribers.length) {
50092             return;
50093         }
50094
50095         Ext.destroy(
50096             me.focusEl,
50097             me.focusFrame
50098         );
50099         delete me.focusEl;
50100         delete me.focusFrame;
50101         delete me.focusFrameWidth;
50102     },
50103
50104     /**
50105      * Removes the specified xtype from the {@link #whitelist}.
50106      * @param {String/String[]} xtype Removes the xtype(s) from the {@link #whitelist}.
50107      */
50108     removeXTypeFromWhitelist: function(xtype) {
50109         var me = this;
50110
50111         if (Ext.isArray(xtype)) {
50112             Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
50113             return;
50114         }
50115
50116         Ext.Array.remove(me.whitelist, xtype);
50117     },
50118
50119     setFocus: function(cmp, focusable, options) {
50120         var me = this,
50121             el, dom, data,
50122
50123             needsTabIndex = function(n) {
50124                 return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
50125                     && n.tabIndex <= 0;
50126             };
50127
50128         options = options || {};
50129
50130         // Come back and do this after the component is rendered
50131         if (!cmp.rendered) {
50132             cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
50133             return;
50134         }
50135
50136         el = cmp.getFocusEl();
50137         dom = el.dom;
50138
50139         // Decorate the component's focus el for focus-ability
50140         if ((focusable && !me.focusData[cmp.id]) || (!focusable && me.focusData[cmp.id])) {
50141             if (focusable) {
50142                 data = {
50143                     focusFrame: options.focusFrame
50144                 };
50145
50146                 // Only set -1 tabIndex if we need it
50147                 // inputs, buttons, and anchor tags do not need it,
50148                 // and neither does any DOM that has it set already
50149                 // programmatically or in markup.
50150                 if (needsTabIndex(dom)) {
50151                     data.tabIndex = dom.tabIndex;
50152                     dom.tabIndex = -1;
50153                 }
50154
50155                 el.on({
50156                     focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
50157                     blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
50158                     scope: me
50159                 });
50160                 cmp.on({
50161                     hide: me.onComponentHide,
50162                     close: me.onComponentHide,
50163                     beforedestroy: me.onComponentDestroy,
50164                     scope: me
50165                 });
50166
50167                 me.focusData[cmp.id] = data;
50168             } else {
50169                 data = me.focusData[cmp.id];
50170                 if ('tabIndex' in data) {
50171                     dom.tabIndex = data.tabIndex;
50172                 }
50173                 el.un('focus', data.focusFn, me);
50174                 el.un('blur', data.blurFn, me);
50175                 cmp.un('hide', me.onComponentHide, me);
50176                 cmp.un('close', me.onComponentHide, me);
50177                 cmp.un('beforedestroy', me.onComponentDestroy, me);
50178
50179                 delete me.focusData[cmp.id];
50180             }
50181         }
50182     },
50183
50184     setFocusAll: function(focusable, options) {
50185         var me = this,
50186             cmps = Ext.ComponentManager.all.getArray(),
50187             len = cmps.length,
50188             cmp,
50189             i = 0;
50190
50191         for (; i < len; i++) {
50192             me.setFocus(cmps[i], focusable, options);
50193         }
50194     },
50195
50196     setupSubscriberKeys: function(container, keys) {
50197         var me = this,
50198             el = container.getFocusEl(),
50199             scope = keys.scope,
50200             handlers = {
50201                 backspace: me.focusLast,
50202                 enter: me.navigateIn,
50203                 esc: me.navigateOut,
50204                 scope: me
50205             },
50206
50207             navSiblings = function(e) {
50208                 if (me.focusedCmp === container) {
50209                     // Root the sibling navigation to this container, so that we
50210                     // can automatically dive into the container, rather than forcing
50211                     // the user to hit the enter key to dive in.
50212                     return me.navigateSiblings(e, me, container);
50213                 } else {
50214                     return me.navigateSiblings(e);
50215                 }
50216             };
50217
50218         Ext.iterate(keys, function(key, cb) {
50219             handlers[key] = function(e) {
50220                 var ret = navSiblings(e);
50221
50222                 if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
50223                     return true;
50224                 }
50225
50226                 return ret;
50227             };
50228         }, me);
50229
50230         return Ext.create('Ext.util.KeyNav', el, handlers);
50231     },
50232
50233     shouldShowFocusFrame: function(cmp) {
50234         var me = this,
50235             opts = me.options || {};
50236
50237         if (!me.focusFrame || !cmp) {
50238             return false;
50239         }
50240
50241         // Global trumps
50242         if (opts.focusFrame) {
50243             return true;
50244         }
50245
50246         if (me.focusData[cmp.id].focusFrame) {
50247             return true;
50248         }
50249
50250         return false;
50251     },
50252
50253     /**
50254      * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
50255      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
50256      * @param {Boolean/Object} options An object of the following options
50257      * @param {Array/Object} options.keys
50258      * An array containing the string names of navigation keys to be supported. The allowed values are:
50259      *
50260      *   - 'left'
50261      *   - 'right'
50262      *   - 'up'
50263      *   - 'down'
50264      *
50265      * 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.:
50266      *
50267      *     {
50268      *         left: this.onLeftKey,
50269      *         right: this.onRightKey,
50270      *         scope: this
50271      *     }
50272      *
50273      * @param {Boolean} options.focusFrame
50274      * `true` to show the focus frame around a component when it is focused. Defaults to `false`.
50275      */
50276     subscribe: function(container, options) {
50277         var me = this,
50278             EA = Ext.Array,
50279             data = {},
50280             subs = me.subscribers,
50281
50282             // Recursively add focus ability as long as a descendent container isn't
50283             // itself subscribed to the FocusManager, or else we'd have unwanted side
50284             // effects for subscribing a descendent container twice.
50285             safeSetFocus = function(cmp) {
50286                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
50287                     EA.forEach(cmp.query('>'), safeSetFocus);
50288                     me.setFocus(cmp, true, options);
50289                     cmp.on('add', data.onAdd, me);
50290                 } else if (!cmp.isContainer) {
50291                     me.setFocus(cmp, true, options);
50292                 }
50293             };
50294
50295         // We only accept containers
50296         if (!container || !container.isContainer) {
50297             return;
50298         }
50299
50300         if (!container.rendered) {
50301             container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
50302             return;
50303         }
50304
50305         // Init the DOM, incase this is the first time it will be used
50306         me.initDOM(options);
50307
50308         // Create key navigation for subscriber based on keys option
50309         data.keyNav = me.setupSubscriberKeys(container, options.keys);
50310
50311         // We need to keep track of components being added to our subscriber
50312         // and any containers nested deeply within it (omg), so let's do that.
50313         // Components that are removed are globally handled.
50314         // Also keep track of destruction of our container for auto-unsubscribe.
50315         data.onAdd = function(ct, cmp, idx) {
50316             safeSetFocus(cmp);
50317         };
50318         container.on('beforedestroy', me.unsubscribe, me);
50319
50320         // Now we setup focusing abilities for the container and all its components
50321         safeSetFocus(container);
50322
50323         // Add to our subscribers list
50324         subs.add(container.id, data);
50325     },
50326
50327     /**
50328      * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
50329      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
50330      */
50331     unsubscribe: function(container) {
50332         var me = this,
50333             EA = Ext.Array,
50334             subs = me.subscribers,
50335             data,
50336
50337             // Recursively remove focus ability as long as a descendent container isn't
50338             // itself subscribed to the FocusManager, or else we'd have unwanted side
50339             // effects for unsubscribing an ancestor container.
50340             safeSetFocus = function(cmp) {
50341                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
50342                     EA.forEach(cmp.query('>'), safeSetFocus);
50343                     me.setFocus(cmp, false);
50344                     cmp.un('add', data.onAdd, me);
50345                 } else if (!cmp.isContainer) {
50346                     me.setFocus(cmp, false);
50347                 }
50348             };
50349
50350         if (!container || !subs.containsKey(container.id)) {
50351             return;
50352         }
50353
50354         data = subs.get(container.id);
50355         data.keyNav.destroy();
50356         container.un('beforedestroy', me.unsubscribe, me);
50357         subs.removeAtKey(container.id);
50358         safeSetFocus(container);
50359         me.removeDOM();
50360     }
50361 });
50362 /**
50363  * Basic Toolbar class. Although the {@link Ext.container.Container#defaultType defaultType} for Toolbar is {@link Ext.button.Button button}, Toolbar
50364  * elements (child items for the Toolbar container) may be virtually any type of Component. Toolbar elements can be created explicitly via their
50365  * constructors, or implicitly via their xtypes, and can be {@link #add}ed dynamically.
50366  *
50367  * ## Some items have shortcut strings for creation:
50368  *
50369  * | Shortcut | xtype         | Class                         | Description
50370  * |:---------|:--------------|:------------------------------|:---------------------------------------------------
50371  * | `->`     | `tbfill`      | {@link Ext.toolbar.Fill}      | begin using the right-justified button container
50372  * | `-`      | `tbseparator` | {@link Ext.toolbar.Separator} | add a vertical separator bar between toolbar items
50373  * | ` `      | `tbspacer`    | {@link Ext.toolbar.Spacer}    | add horiztonal space between elements
50374  *
50375  *     @example
50376  *     Ext.create('Ext.toolbar.Toolbar', {
50377  *         renderTo: document.body,
50378  *         width   : 500,
50379  *         items: [
50380  *             {
50381  *                 // xtype: 'button', // default for Toolbars
50382  *                 text: 'Button'
50383  *             },
50384  *             {
50385  *                 xtype: 'splitbutton',
50386  *                 text : 'Split Button'
50387  *             },
50388  *             // begin using the right-justified button container
50389  *             '->', // same as { xtype: 'tbfill' }
50390  *             {
50391  *                 xtype    : 'textfield',
50392  *                 name     : 'field1',
50393  *                 emptyText: 'enter search term'
50394  *             },
50395  *             // add a vertical separator bar between toolbar items
50396  *             '-', // same as {xtype: 'tbseparator'} to create Ext.toolbar.Separator
50397  *             'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.toolbar.TextItem
50398  *             { xtype: 'tbspacer' },// same as ' ' to create Ext.toolbar.Spacer
50399  *             'text 2',
50400  *             { xtype: 'tbspacer', width: 50 }, // add a 50px space
50401  *             'text 3'
50402  *         ]
50403  *     });
50404  *
50405  * Toolbars have {@link #enable} and {@link #disable} methods which when called, will enable/disable all items within your toolbar.
50406  *
50407  *     @example
50408  *     Ext.create('Ext.toolbar.Toolbar', {
50409  *         renderTo: document.body,
50410  *         width   : 400,
50411  *         items: [
50412  *             {
50413  *                 text: 'Button'
50414  *             },
50415  *             {
50416  *                 xtype: 'splitbutton',
50417  *                 text : 'Split Button'
50418  *             },
50419  *             '->',
50420  *             {
50421  *                 xtype    : 'textfield',
50422  *                 name     : 'field1',
50423  *                 emptyText: 'enter search term'
50424  *             }
50425  *         ]
50426  *     });
50427  *
50428  * Example
50429  *
50430  *     @example
50431  *     var enableBtn = Ext.create('Ext.button.Button', {
50432  *         text    : 'Enable All Items',
50433  *         disabled: true,
50434  *         scope   : this,
50435  *         handler : function() {
50436  *             //disable the enable button and enable the disable button
50437  *             enableBtn.disable();
50438  *             disableBtn.enable();
50439  *
50440  *             //enable the toolbar
50441  *             toolbar.enable();
50442  *         }
50443  *     });
50444  *
50445  *     var disableBtn = Ext.create('Ext.button.Button', {
50446  *         text    : 'Disable All Items',
50447  *         scope   : this,
50448  *         handler : function() {
50449  *             //enable the enable button and disable button
50450  *             disableBtn.disable();
50451  *             enableBtn.enable();
50452  *
50453  *             //disable the toolbar
50454  *             toolbar.disable();
50455  *         }
50456  *     });
50457  *
50458  *     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
50459  *         renderTo: document.body,
50460  *         width   : 400,
50461  *         margin  : '5 0 0 0',
50462  *         items   : [enableBtn, disableBtn]
50463  *     });
50464  *
50465  * 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
50466  * which remove all items within the toolbar.
50467  *
50468  *     @example
50469  *     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
50470  *         renderTo: document.body,
50471  *         width   : 700,
50472  *         items: [
50473  *             {
50474  *                 text: 'Example Button'
50475  *             }
50476  *         ]
50477  *     });
50478  *
50479  *     var addedItems = [];
50480  *
50481  *     Ext.create('Ext.toolbar.Toolbar', {
50482  *         renderTo: document.body,
50483  *         width   : 700,
50484  *         margin  : '5 0 0 0',
50485  *         items   : [
50486  *             {
50487  *                 text   : 'Add a button',
50488  *                 scope  : this,
50489  *                 handler: function() {
50490  *                     var text = prompt('Please enter the text for your button:');
50491  *                     addedItems.push(toolbar.add({
50492  *                         text: text
50493  *                     }));
50494  *                 }
50495  *             },
50496  *             {
50497  *                 text   : 'Add a text item',
50498  *                 scope  : this,
50499  *                 handler: function() {
50500  *                     var text = prompt('Please enter the text for your item:');
50501  *                     addedItems.push(toolbar.add(text));
50502  *                 }
50503  *             },
50504  *             {
50505  *                 text   : 'Add a toolbar seperator',
50506  *                 scope  : this,
50507  *                 handler: function() {
50508  *                     addedItems.push(toolbar.add('-'));
50509  *                 }
50510  *             },
50511  *             {
50512  *                 text   : 'Add a toolbar spacer',
50513  *                 scope  : this,
50514  *                 handler: function() {
50515  *                     addedItems.push(toolbar.add('->'));
50516  *                 }
50517  *             },
50518  *             '->',
50519  *             {
50520  *                 text   : 'Remove last inserted item',
50521  *                 scope  : this,
50522  *                 handler: function() {
50523  *                     if (addedItems.length) {
50524  *                         toolbar.remove(addedItems.pop());
50525  *                     } else if (toolbar.items.length) {
50526  *                         toolbar.remove(toolbar.items.last());
50527  *                     } else {
50528  *                         alert('No items in the toolbar');
50529  *                     }
50530  *                 }
50531  *             },
50532  *             {
50533  *                 text   : 'Remove all items',
50534  *                 scope  : this,
50535  *                 handler: function() {
50536  *                     toolbar.removeAll();
50537  *                 }
50538  *             }
50539  *         ]
50540  *     });
50541  *
50542  * @constructor
50543  * Creates a new Toolbar
50544  * @param {Object/Object[]} config A config object or an array of buttons to <code>{@link #add}</code>
50545  * @docauthor Robert Dougan <rob@sencha.com>
50546  */
50547 Ext.define('Ext.toolbar.Toolbar', {
50548     extend: 'Ext.container.Container',
50549     requires: [
50550         'Ext.toolbar.Fill',
50551         'Ext.layout.container.HBox',
50552         'Ext.layout.container.VBox',
50553         'Ext.FocusManager'
50554     ],
50555     uses: [
50556         'Ext.toolbar.Separator'
50557     ],
50558     alias: 'widget.toolbar',
50559     alternateClassName: 'Ext.Toolbar',
50560
50561     isToolbar: true,
50562     baseCls  : Ext.baseCSSPrefix + 'toolbar',
50563     ariaRole : 'toolbar',
50564
50565     defaultType: 'button',
50566
50567     /**
50568      * @cfg {Boolean} vertical
50569      * Set to `true` to make the toolbar vertical. The layout will become a `vbox`.
50570      */
50571     vertical: false,
50572
50573     /**
50574      * @cfg {String/Object} layout
50575      * This class assigns a default layout (`layout: 'hbox'`).
50576      * Developers _may_ override this configuration option if another layout
50577      * is required (the constructor must be passed a configuration object in this
50578      * case instead of an array).
50579      * See {@link Ext.container.Container#layout} for additional information.
50580      */
50581
50582     /**
50583      * @cfg {Boolean} enableOverflow
50584      * Configure true to make the toolbar provide a button which activates a dropdown Menu to show
50585      * items which overflow the Toolbar's width.
50586      */
50587     enableOverflow: false,
50588
50589     /**
50590      * @cfg {String} menuTriggerCls
50591      * Configure the icon class of the overflow button.
50592      */
50593     menuTriggerCls: Ext.baseCSSPrefix + 'toolbar-more-icon',
50594     
50595     // private
50596     trackMenus: true,
50597
50598     itemCls: Ext.baseCSSPrefix + 'toolbar-item',
50599
50600     initComponent: function() {
50601         var me = this,
50602             keys;
50603
50604         // check for simplified (old-style) overflow config:
50605         if (!me.layout && me.enableOverflow) {
50606             me.layout = { overflowHandler: 'Menu' };
50607         }
50608
50609         if (me.dock === 'right' || me.dock === 'left') {
50610             me.vertical = true;
50611         }
50612
50613         me.layout = Ext.applyIf(Ext.isString(me.layout) ? {
50614             type: me.layout
50615         } : me.layout || {}, {
50616             type: me.vertical ? 'vbox' : 'hbox',
50617             align: me.vertical ? 'stretchmax' : 'middle',
50618             clearInnerCtOnLayout: true
50619         });
50620
50621         if (me.vertical) {
50622             me.addClsWithUI('vertical');
50623         }
50624
50625         // @TODO: remove this hack and implement a more general solution
50626         if (me.ui === 'footer') {
50627             me.ignoreBorderManagement = true;
50628         }
50629
50630         me.callParent();
50631
50632         /**
50633          * @event overflowchange
50634          * Fires after the overflow state has changed.
50635          * @param {Object} c The Container
50636          * @param {Boolean} lastOverflow overflow state
50637          */
50638         me.addEvents('overflowchange');
50639
50640         // Subscribe to Ext.FocusManager for key navigation
50641         keys = me.vertical ? ['up', 'down'] : ['left', 'right'];
50642         Ext.FocusManager.subscribe(me, {
50643             keys: keys
50644         });
50645     },
50646
50647     getRefItems: function(deep) {
50648         var me = this,
50649             items = me.callParent(arguments),
50650             layout = me.layout,
50651             handler;
50652
50653         if (deep && me.enableOverflow) {
50654             handler = layout.overflowHandler;
50655             if (handler && handler.menu) {
50656                 items = items.concat(handler.menu.getRefItems(deep));
50657             }
50658         }
50659         return items;
50660     },
50661
50662     /**
50663      * Adds element(s) to the toolbar -- this function takes a variable number of
50664      * arguments of mixed type and adds them to the toolbar.
50665      *
50666      * **Note**: See the notes within {@link Ext.container.Container#add}.
50667      *
50668      * @param {Object...} args The following types of arguments are all valid:
50669      *  - `{@link Ext.button.Button config}`: A valid button config object
50670      *  - `HtmlElement`: Any standard HTML element
50671      *  - `Field`: Any form field
50672      *  - `Item`: Any subclass of {@link Ext.toolbar.Item}
50673      *  - `String`: Any generic string (gets wrapped in a {@link Ext.toolbar.TextItem}).
50674      *  Note that there are a few special strings that are treated differently as explained next.
50675      *  - `'-'`: Creates a separator element
50676      *  - `' '`: Creates a spacer element
50677      *  - `'->'`: Creates a fill element
50678      *
50679      * @method add
50680      */
50681
50682     // private
50683     lookupComponent: function(c) {
50684         if (Ext.isString(c)) {
50685             var shortcut = Ext.toolbar.Toolbar.shortcuts[c];
50686             if (shortcut) {
50687                 c = {
50688                     xtype: shortcut
50689                 };
50690             } else {
50691                 c = {
50692                     xtype: 'tbtext',
50693                     text: c
50694                 };
50695             }
50696             this.applyDefaults(c);
50697         }
50698         return this.callParent(arguments);
50699     },
50700
50701     // private
50702     applyDefaults: function(c) {
50703         if (!Ext.isString(c)) {
50704             c = this.callParent(arguments);
50705             var d = this.internalDefaults;
50706             if (c.events) {
50707                 Ext.applyIf(c.initialConfig, d);
50708                 Ext.apply(c, d);
50709             } else {
50710                 Ext.applyIf(c, d);
50711             }
50712         }
50713         return c;
50714     },
50715
50716     // private
50717     trackMenu: function(item, remove) {
50718         if (this.trackMenus && item.menu) {
50719             var method = remove ? 'mun' : 'mon',
50720                 me = this;
50721
50722             me[method](item, 'mouseover', me.onButtonOver, me);
50723             me[method](item, 'menushow', me.onButtonMenuShow, me);
50724             me[method](item, 'menuhide', me.onButtonMenuHide, me);
50725         }
50726     },
50727
50728     // private
50729     constructButton: function(item) {
50730         return item.events ? item : this.createComponent(item, item.split ? 'splitbutton' : this.defaultType);
50731     },
50732
50733     // private
50734     onBeforeAdd: function(component) {
50735         if (component.is('field') || (component.is('button') && this.ui != 'footer')) {
50736             component.ui = component.ui + '-toolbar';
50737         }
50738
50739         // Any separators needs to know if is vertical or not
50740         if (component instanceof Ext.toolbar.Separator) {
50741             component.setUI((this.vertical) ? 'vertical' : 'horizontal');
50742         }
50743
50744         this.callParent(arguments);
50745     },
50746
50747     // private
50748     onAdd: function(component) {
50749         this.callParent(arguments);
50750
50751         this.trackMenu(component);
50752         if (this.disabled) {
50753             component.disable();
50754         }
50755     },
50756
50757     // private
50758     onRemove: function(c) {
50759         this.callParent(arguments);
50760         this.trackMenu(c, true);
50761     },
50762
50763     // private
50764     onButtonOver: function(btn){
50765         if (this.activeMenuBtn && this.activeMenuBtn != btn) {
50766             this.activeMenuBtn.hideMenu();
50767             btn.showMenu();
50768             this.activeMenuBtn = btn;
50769         }
50770     },
50771
50772     // private
50773     onButtonMenuShow: function(btn) {
50774         this.activeMenuBtn = btn;
50775     },
50776
50777     // private
50778     onButtonMenuHide: function(btn) {
50779         delete this.activeMenuBtn;
50780     }
50781 }, function() {
50782     this.shortcuts = {
50783         '-' : 'tbseparator',
50784         ' ' : 'tbspacer',
50785         '->': 'tbfill'
50786     };
50787 });
50788 /**
50789  * @class Ext.panel.AbstractPanel
50790  * @extends Ext.container.Container
50791  * A base class which provides methods common to Panel classes across the Sencha product range.
50792  * @private
50793  */
50794 Ext.define('Ext.panel.AbstractPanel', {
50795
50796     /* Begin Definitions */
50797
50798     extend: 'Ext.container.Container',
50799
50800     requires: ['Ext.util.MixedCollection', 'Ext.Element', 'Ext.toolbar.Toolbar'],
50801
50802     /* End Definitions */
50803
50804     /**
50805      * @cfg {String} [baseCls='x-panel']
50806      * The base CSS class to apply to this panel's element.
50807      */
50808     baseCls : Ext.baseCSSPrefix + 'panel',
50809
50810     /**
50811      * @cfg {Number/String} bodyPadding
50812      * A shortcut for setting a padding style on the body element. The value can either be
50813      * a number to be applied to all sides, or a normal css string describing padding.
50814      */
50815
50816     /**
50817      * @cfg {Boolean} bodyBorder
50818      * A shortcut to add or remove the border on the body of a panel. This only applies to a panel
50819      * which has the {@link #frame} configuration set to `true`.
50820      */
50821
50822     /**
50823      * @cfg {String/Object/Function} bodyStyle
50824      * Custom CSS styles to be applied to the panel's body element, which can be supplied as a valid CSS style string,
50825      * an object containing style property name/value pairs or a function that returns such a string or object.
50826      * For example, these two formats are interpreted to be equivalent:<pre><code>
50827 bodyStyle: 'background:#ffc; padding:10px;'
50828
50829 bodyStyle: {
50830     background: '#ffc',
50831     padding: '10px'
50832 }
50833      * </code></pre>
50834      */
50835
50836     /**
50837      * @cfg {String/String[]} bodyCls
50838      * A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
50839      * The following examples are all valid:<pre><code>
50840 bodyCls: 'foo'
50841 bodyCls: 'foo bar'
50842 bodyCls: ['foo', 'bar']
50843      * </code></pre>
50844      */
50845
50846     isPanel: true,
50847
50848     componentLayout: 'dock',
50849
50850     /**
50851      * @cfg {Object} defaultDockWeights
50852      * This object holds the default weights applied to dockedItems that have no weight. These start with a
50853      * weight of 1, to allow negative weights to insert before top items and are odd numbers
50854      * so that even weights can be used to get between different dock orders.
50855      *
50856      * To make default docking order match border layout, do this:
50857      * <pre><code>
50858 Ext.panel.AbstractPanel.prototype.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };</code></pre>
50859      * Changing these defaults as above or individually on this object will effect all Panels.
50860      * To change the defaults on a single panel, you should replace the entire object:
50861      * <pre><code>
50862 initComponent: function () {
50863     // NOTE: Don't change members of defaultDockWeights since the object is shared.
50864     this.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };
50865
50866     this.callParent();
50867 }</code></pre>
50868      *
50869      * To change only one of the default values, you do this:
50870      * <pre><code>
50871 initComponent: function () {
50872     // NOTE: Don't change members of defaultDockWeights since the object is shared.
50873     this.defaultDockWeights = Ext.applyIf({ top: 10 }, this.defaultDockWeights);
50874
50875     this.callParent();
50876 }</code></pre>
50877      */
50878     defaultDockWeights: { top: 1, left: 3, right: 5, bottom: 7 },
50879
50880     renderTpl: [
50881         '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
50882             ' {baseCls}-body-{ui}<tpl if="uiCls">',
50883                 '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
50884             '</tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',
50885         '</div>'
50886     ],
50887
50888     // TODO: Move code examples into product-specific files. The code snippet below is Touch only.
50889     /**
50890      * @cfg {Object/Object[]} dockedItems
50891      * A component or series of components to be added as docked items to this panel.
50892      * The docked items can be docked to either the top, right, left or bottom of a panel.
50893      * This is typically used for things like toolbars or tab bars:
50894      * <pre><code>
50895 var panel = new Ext.panel.Panel({
50896     fullscreen: true,
50897     dockedItems: [{
50898         xtype: 'toolbar',
50899         dock: 'top',
50900         items: [{
50901             text: 'Docked to the top'
50902         }]
50903     }]
50904 });</code></pre>
50905      */
50906
50907     border: true,
50908
50909     initComponent : function() {
50910         var me = this;
50911
50912         me.addEvents(
50913             /**
50914              * @event bodyresize
50915              * Fires after the Panel has been resized.
50916              * @param {Ext.panel.Panel} p the Panel which has been resized.
50917              * @param {Number} width The Panel body's new width.
50918              * @param {Number} height The Panel body's new height.
50919              */
50920             'bodyresize'
50921             // // inherited
50922             // 'activate',
50923             // // inherited
50924             // 'deactivate'
50925         );
50926
50927         me.addChildEls('body');
50928
50929         //!frame
50930         //!border
50931
50932         if (me.frame && me.border && me.bodyBorder === undefined) {
50933             me.bodyBorder = false;
50934         }
50935         if (me.frame && me.border && (me.bodyBorder === false || me.bodyBorder === 0)) {
50936             me.manageBodyBorders = true;
50937         }
50938
50939         me.callParent();
50940     },
50941
50942     // @private
50943     initItems : function() {
50944         var me = this,
50945             items = me.dockedItems;
50946
50947         me.callParent();
50948         me.dockedItems = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
50949         if (items) {
50950             me.addDocked(items);
50951         }
50952     },
50953
50954     /**
50955      * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
50956      * @param {String/Number} comp The id, itemId or position of the docked component (see {@link #getComponent} for details)
50957      * @return {Ext.Component} The docked component (if found)
50958      */
50959     getDockedComponent: function(comp) {
50960         if (Ext.isObject(comp)) {
50961             comp = comp.getItemId();
50962         }
50963         return this.dockedItems.get(comp);
50964     },
50965
50966     /**
50967      * Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
50968      * items, the dockedItems are searched and the matched component (if any) returned (see {@link #getDockedComponent}). Note that docked
50969      * items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
50970      * @param {String/Number} comp The component id, itemId or position to find
50971      * @return {Ext.Component} The component (if found)
50972      */
50973     getComponent: function(comp) {
50974         var component = this.callParent(arguments);
50975         if (component === undefined && !Ext.isNumber(comp)) {
50976             // If the arg is a numeric index skip docked items
50977             component = this.getDockedComponent(comp);
50978         }
50979         return component;
50980     },
50981
50982     /**
50983      * Parses the {@link bodyStyle} config if available to create a style string that will be applied to the body element.
50984      * This also includes {@link bodyPadding} and {@link bodyBorder} if available.
50985      * @return {String} A CSS style string with body styles, padding and border.
50986      * @private
50987      */
50988     initBodyStyles: function() {
50989         var me = this,
50990             bodyStyle = me.bodyStyle,
50991             styles = [],
50992             Element = Ext.Element,
50993             prop;
50994
50995         if (Ext.isFunction(bodyStyle)) {
50996             bodyStyle = bodyStyle();
50997         }
50998         if (Ext.isString(bodyStyle)) {
50999             styles = bodyStyle.split(';');
51000         } else {
51001             for (prop in bodyStyle) {
51002                 if (bodyStyle.hasOwnProperty(prop)) {
51003                     styles.push(prop + ':' + bodyStyle[prop]);
51004                 }
51005             }
51006         }
51007
51008         if (me.bodyPadding !== undefined) {
51009             styles.push('padding: ' + Element.unitizeBox((me.bodyPadding === true) ? 5 : me.bodyPadding));
51010         }
51011         if (me.frame && me.bodyBorder) {
51012             if (!Ext.isNumber(me.bodyBorder)) {
51013                 me.bodyBorder = 1;
51014             }
51015             styles.push('border-width: ' + Element.unitizeBox(me.bodyBorder));
51016         }
51017         delete me.bodyStyle;
51018         return styles.length ? styles.join(';') : undefined;
51019     },
51020
51021     /**
51022      * Parse the {@link bodyCls} config if available to create a comma-delimited string of
51023      * CSS classes to be applied to the body element.
51024      * @return {String} The CSS class(es)
51025      * @private
51026      */
51027     initBodyCls: function() {
51028         var me = this,
51029             cls = '',
51030             bodyCls = me.bodyCls;
51031
51032         if (bodyCls) {
51033             Ext.each(bodyCls, function(v) {
51034                 cls += " " + v;
51035             });
51036             delete me.bodyCls;
51037         }
51038         return cls.length > 0 ? cls : undefined;
51039     },
51040
51041     /**
51042      * Initialized the renderData to be used when rendering the renderTpl.
51043      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
51044      * @private
51045      */
51046     initRenderData: function() {
51047         return Ext.applyIf(this.callParent(), {
51048             bodyStyle: this.initBodyStyles(),
51049             bodyCls: this.initBodyCls()
51050         });
51051     },
51052
51053     /**
51054      * Adds docked item(s) to the panel.
51055      * @param {Object/Object[]} component The Component or array of components to add. The components
51056      * must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
51057      * 'bottom', 'left').
51058      * @param {Number} pos (optional) The index at which the Component will be added
51059      */
51060     addDocked : function(items, pos) {
51061         var me = this,
51062             i = 0,
51063             item, length;
51064
51065         items = me.prepareItems(items);
51066         length = items.length;
51067
51068         for (; i < length; i++) {
51069             item = items[i];
51070             item.dock = item.dock || 'top';
51071
51072             // Allow older browsers to target docked items to style without borders
51073             if (me.border === false) {
51074                 // item.cls = item.cls || '' + ' ' + me.baseCls + '-noborder-docked-' + item.dock;
51075             }
51076
51077             if (pos !== undefined) {
51078                 me.dockedItems.insert(pos + i, item);
51079             }
51080             else {
51081                 me.dockedItems.add(item);
51082             }
51083             item.onAdded(me, i);
51084             me.onDockedAdd(item);
51085         }
51086
51087         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
51088         me.componentLayout.childrenChanged = true;
51089         if (me.rendered && !me.suspendLayout) {
51090             me.doComponentLayout();
51091         }
51092         return items;
51093     },
51094
51095     // Placeholder empty functions
51096     onDockedAdd : Ext.emptyFn,
51097     onDockedRemove : Ext.emptyFn,
51098
51099     /**
51100      * Inserts docked item(s) to the panel at the indicated position.
51101      * @param {Number} pos The index at which the Component will be inserted
51102      * @param {Object/Object[]} component. The Component or array of components to add. The components
51103      * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
51104      * 'bottom', 'left').
51105      */
51106     insertDocked : function(pos, items) {
51107         this.addDocked(items, pos);
51108     },
51109
51110     /**
51111      * Removes the docked item from the panel.
51112      * @param {Ext.Component} item. The Component to remove.
51113      * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
51114      */
51115     removeDocked : function(item, autoDestroy) {
51116         var me = this,
51117             layout,
51118             hasLayout;
51119
51120         if (!me.dockedItems.contains(item)) {
51121             return item;
51122         }
51123
51124         layout = me.componentLayout;
51125         hasLayout = layout && me.rendered;
51126
51127         if (hasLayout) {
51128             layout.onRemove(item);
51129         }
51130
51131         me.dockedItems.remove(item);
51132         item.onRemoved();
51133         me.onDockedRemove(item);
51134
51135         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
51136             item.destroy();
51137         } else if (hasLayout) {
51138             // not destroying, make any layout related removals
51139             layout.afterRemove(item);    
51140         }
51141
51142
51143         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
51144         me.componentLayout.childrenChanged = true;
51145         if (!me.destroying && !me.suspendLayout) {
51146             me.doComponentLayout();
51147         }
51148
51149         return item;
51150     },
51151
51152     /**
51153      * Retrieve an array of all currently docked Components.
51154      * @param {String} cqSelector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
51155      * @return {Ext.Component[]} An array of components.
51156      */
51157     getDockedItems : function(cqSelector) {
51158         var me = this,
51159             defaultWeight = me.defaultDockWeights,
51160             dockedItems;
51161
51162         if (me.dockedItems && me.dockedItems.items.length) {
51163             // Allow filtering of returned docked items by CQ selector.
51164             if (cqSelector) {
51165                 dockedItems = Ext.ComponentQuery.query(cqSelector, me.dockedItems.items);
51166             } else {
51167                 dockedItems = me.dockedItems.items.slice();
51168             }
51169
51170             Ext.Array.sort(dockedItems, function(a, b) {
51171                 // Docked items are ordered by their visual representation by default (t,l,r,b)
51172                 var aw = a.weight || defaultWeight[a.dock],
51173                     bw = b.weight || defaultWeight[b.dock];
51174                 if (Ext.isNumber(aw) && Ext.isNumber(bw)) {
51175                     return aw - bw;
51176                 }
51177                 return 0;
51178             });
51179
51180             return dockedItems;
51181         }
51182         return [];
51183     },
51184
51185     // inherit docs
51186     addUIClsToElement: function(cls, force) {
51187         var me = this,
51188             result = me.callParent(arguments),
51189             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
51190             array, i;
51191
51192         if (!force && me.rendered) {
51193             if (me.bodyCls) {
51194                 me.body.addCls(me.bodyCls);
51195             } else {
51196                 me.body.addCls(classes);
51197             }
51198         } else {
51199             if (me.bodyCls) {
51200                 array = me.bodyCls.split(' ');
51201
51202                 for (i = 0; i < classes.length; i++) {
51203                     if (!Ext.Array.contains(array, classes[i])) {
51204                         array.push(classes[i]);
51205                     }
51206                 }
51207
51208                 me.bodyCls = array.join(' ');
51209             } else {
51210                 me.bodyCls = classes.join(' ');
51211             }
51212         }
51213
51214         return result;
51215     },
51216
51217     // inherit docs
51218     removeUIClsFromElement: function(cls, force) {
51219         var me = this,
51220             result = me.callParent(arguments),
51221             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
51222             array, i;
51223
51224         if (!force && me.rendered) {
51225             if (me.bodyCls) {
51226                 me.body.removeCls(me.bodyCls);
51227             } else {
51228                 me.body.removeCls(classes);
51229             }
51230         } else {
51231             if (me.bodyCls) {
51232                 array = me.bodyCls.split(' ');
51233
51234                 for (i = 0; i < classes.length; i++) {
51235                     Ext.Array.remove(array, classes[i]);
51236                 }
51237
51238                 me.bodyCls = array.join(' ');
51239             }
51240         }
51241
51242         return result;
51243     },
51244
51245     // inherit docs
51246     addUIToElement: function(force) {
51247         var me = this,
51248             cls = me.baseCls + '-body-' + me.ui,
51249             array;
51250
51251         me.callParent(arguments);
51252
51253         if (!force && me.rendered) {
51254             if (me.bodyCls) {
51255                 me.body.addCls(me.bodyCls);
51256             } else {
51257                 me.body.addCls(cls);
51258             }
51259         } else {
51260             if (me.bodyCls) {
51261                 array = me.bodyCls.split(' ');
51262
51263                 if (!Ext.Array.contains(array, cls)) {
51264                     array.push(cls);
51265                 }
51266
51267                 me.bodyCls = array.join(' ');
51268             } else {
51269                 me.bodyCls = cls;
51270             }
51271         }
51272     },
51273
51274     // inherit docs
51275     removeUIFromElement: function() {
51276         var me = this,
51277             cls = me.baseCls + '-body-' + me.ui,
51278             array;
51279
51280         me.callParent(arguments);
51281
51282         if (me.rendered) {
51283             if (me.bodyCls) {
51284                 me.body.removeCls(me.bodyCls);
51285             } else {
51286                 me.body.removeCls(cls);
51287             }
51288         } else {
51289             if (me.bodyCls) {
51290                 array = me.bodyCls.split(' ');
51291                 Ext.Array.remove(array, cls);
51292                 me.bodyCls = array.join(' ');
51293             } else {
51294                 me.bodyCls = cls;
51295             }
51296         }
51297     },
51298
51299     // @private
51300     getTargetEl : function() {
51301         return this.body;
51302     },
51303
51304     getRefItems: function(deep) {
51305         var items = this.callParent(arguments),
51306             // deep fetches all docked items, and their descendants using '*' selector and then '* *'
51307             dockedItems = this.getDockedItems(deep ? '*,* *' : undefined),
51308             ln = dockedItems.length,
51309             i = 0,
51310             item;
51311
51312         // Find the index where we go from top/left docked items to right/bottom docked items
51313         for (; i < ln; i++) {
51314             item = dockedItems[i];
51315             if (item.dock === 'right' || item.dock === 'bottom') {
51316                 break;
51317             }
51318         }
51319
51320         // Return docked items in the top/left position before our container items, and
51321         // return right/bottom positioned items after our container items.
51322         // See AbstractDock.renderItems() for more information.
51323         return Ext.Array.splice(dockedItems, 0, i).concat(items).concat(dockedItems);
51324     },
51325
51326     beforeDestroy: function(){
51327         var docked = this.dockedItems,
51328             c;
51329
51330         if (docked) {
51331             while ((c = docked.first())) {
51332                 this.removeDocked(c, true);
51333             }
51334         }
51335         this.callParent();
51336     },
51337
51338     setBorder: function(border) {
51339         var me = this;
51340         me.border = (border !== undefined) ? border : true;
51341         if (me.rendered) {
51342             me.doComponentLayout();
51343         }
51344     }
51345 });
51346 /**
51347  * @class Ext.panel.Header
51348  * @extends Ext.container.Container
51349  * Simple header class which is used for on {@link Ext.panel.Panel} and {@link Ext.window.Window}
51350  */
51351 Ext.define('Ext.panel.Header', {
51352     extend: 'Ext.container.Container',
51353     uses: ['Ext.panel.Tool', 'Ext.draw.Component', 'Ext.util.CSS'],
51354     alias: 'widget.header',
51355
51356     isHeader       : true,
51357     defaultType    : 'tool',
51358     indicateDrag   : false,
51359     weight         : -1,
51360
51361     renderTpl: [
51362         '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
51363         '<tpl if="uiCls">',
51364             '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
51365         '</tpl>"',
51366         '<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>'],
51367
51368     /**
51369      * @cfg {String} title
51370      * The title text to display
51371      */
51372
51373     /**
51374      * @cfg {String} iconCls
51375      * CSS class for icon in header. Used for displaying an icon to the left of a title.
51376      */
51377
51378     initComponent: function() {
51379         var me = this,
51380             ruleStyle,
51381             rule,
51382             style,
51383             titleTextEl,
51384             ui;
51385
51386         me.indicateDragCls = me.baseCls + '-draggable';
51387         me.title = me.title || '&#160;';
51388         me.tools = me.tools || [];
51389         me.items = me.items || [];
51390         me.orientation = me.orientation || 'horizontal';
51391         me.dock = (me.dock) ? me.dock : (me.orientation == 'horizontal') ? 'top' : 'left';
51392
51393         //add the dock as a ui
51394         //this is so we support top/right/left/bottom headers
51395         me.addClsWithUI(me.orientation);
51396         me.addClsWithUI(me.dock);
51397
51398         me.addChildEls('body');
51399
51400         // Add Icon
51401         if (!Ext.isEmpty(me.iconCls)) {
51402             me.initIconCmp();
51403             me.items.push(me.iconCmp);
51404         }
51405
51406         // Add Title
51407         if (me.orientation == 'vertical') {
51408             // Hack for IE6/7's inability to display an inline-block
51409             if (Ext.isIE6 || Ext.isIE7) {
51410                 me.width = this.width || 24;
51411             } else if (Ext.isIEQuirks) {
51412                 me.width = this.width || 25;
51413             }
51414
51415             me.layout = {
51416                 type : 'vbox',
51417                 align: 'center',
51418                 clearInnerCtOnLayout: true,
51419                 bindToOwnerCtContainer: false
51420             };
51421             me.textConfig = {
51422                 cls: me.baseCls + '-text',
51423                 type: 'text',
51424                 text: me.title,
51425                 rotate: {
51426                     degrees: 90
51427                 }
51428             };
51429             ui = me.ui;
51430             if (Ext.isArray(ui)) {
51431                 ui = ui[0];
51432             }
51433             ruleStyle = '.' + me.baseCls + '-text-' + ui;
51434             if (Ext.scopeResetCSS) {
51435                 ruleStyle = '.' + Ext.baseCSSPrefix + 'reset ' + ruleStyle;
51436             }
51437             rule = Ext.util.CSS.getRule(ruleStyle);
51438             if (rule) {
51439                 style = rule.style;
51440             }
51441             if (style) {
51442                 Ext.apply(me.textConfig, {
51443                     'font-family': style.fontFamily,
51444                     'font-weight': style.fontWeight,
51445                     'font-size': style.fontSize,
51446                     fill: style.color
51447                 });
51448             }
51449             me.titleCmp = Ext.create('Ext.draw.Component', {
51450                 ariaRole  : 'heading',
51451                 focusable: false,
51452                 viewBox: false,
51453                 flex : 1,
51454                 autoSize: true,
51455                 margins: '5 0 0 0',
51456                 items: [ me.textConfig ],
51457                 // this is a bit of a cheat: we are not selecting an element of titleCmp
51458                 // but rather of titleCmp.items[0] (so we cannot use childEls)
51459                 renderSelectors: {
51460                     textEl: '.' + me.baseCls + '-text'
51461                 }
51462             });
51463         } else {
51464             me.layout = {
51465                 type : 'hbox',
51466                 align: 'middle',
51467                 clearInnerCtOnLayout: true,
51468                 bindToOwnerCtContainer: false
51469             };
51470             me.titleCmp = Ext.create('Ext.Component', {
51471                 xtype     : 'component',
51472                 ariaRole  : 'heading',
51473                 focusable: false,
51474                 flex : 1,
51475                 cls: me.baseCls + '-text-container',
51476                 renderTpl : [
51477                     '<span id="{id}-textEl" class="{cls}-text {cls}-text-{ui}">{title}</span>'
51478                 ],
51479                 renderData: {
51480                     title: me.title,
51481                     cls  : me.baseCls,
51482                     ui   : me.ui
51483                 },
51484                 childEls: ['textEl']
51485             });
51486         }
51487         me.items.push(me.titleCmp);
51488
51489         // Add Tools
51490         me.items = me.items.concat(me.tools);
51491         this.callParent();
51492     },
51493
51494     initIconCmp: function() {
51495         this.iconCmp = Ext.create('Ext.Component', {
51496             focusable: false,
51497             renderTpl : [
51498                 '<img id="{id}-iconEl" alt="" src="{blank}" class="{cls}-icon {iconCls}"/>'
51499             ],
51500             renderData: {
51501                 blank  : Ext.BLANK_IMAGE_URL,
51502                 cls    : this.baseCls,
51503                 iconCls: this.iconCls,
51504                 orientation: this.orientation
51505             },
51506             childEls: ['iconEl'],
51507             iconCls: this.iconCls
51508         });
51509     },
51510
51511     afterRender: function() {
51512         var me = this;
51513
51514         me.el.unselectable();
51515         if (me.indicateDrag) {
51516             me.el.addCls(me.indicateDragCls);
51517         }
51518         me.mon(me.el, {
51519             click: me.onClick,
51520             scope: me
51521         });
51522         me.callParent();
51523     },
51524
51525     afterLayout: function() {
51526         var me = this;
51527         me.callParent(arguments);
51528
51529         // IE7 needs a forced repaint to make the top framing div expand to full width
51530         if (Ext.isIE7) {
51531             me.el.repaint();
51532         }
51533     },
51534
51535     // inherit docs
51536     addUIClsToElement: function(cls, force) {
51537         var me = this,
51538             result = me.callParent(arguments),
51539             classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
51540             array, i;
51541
51542         if (!force && me.rendered) {
51543             if (me.bodyCls) {
51544                 me.body.addCls(me.bodyCls);
51545             } else {
51546                 me.body.addCls(classes);
51547             }
51548         } else {
51549             if (me.bodyCls) {
51550                 array = me.bodyCls.split(' ');
51551
51552                 for (i = 0; i < classes.length; i++) {
51553                     if (!Ext.Array.contains(array, classes[i])) {
51554                         array.push(classes[i]);
51555                     }
51556                 }
51557
51558                 me.bodyCls = array.join(' ');
51559             } else {
51560                 me.bodyCls = classes.join(' ');
51561             }
51562         }
51563
51564         return result;
51565     },
51566
51567     // inherit docs
51568     removeUIClsFromElement: function(cls, force) {
51569         var me = this,
51570             result = me.callParent(arguments),
51571             classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
51572             array, i;
51573
51574         if (!force && me.rendered) {
51575             if (me.bodyCls) {
51576                 me.body.removeCls(me.bodyCls);
51577             } else {
51578                 me.body.removeCls(classes);
51579             }
51580         } else {
51581             if (me.bodyCls) {
51582                 array = me.bodyCls.split(' ');
51583
51584                 for (i = 0; i < classes.length; i++) {
51585                     Ext.Array.remove(array, classes[i]);
51586                 }
51587
51588                 me.bodyCls = array.join(' ');
51589             }
51590         }
51591
51592        return result;
51593     },
51594
51595     // inherit docs
51596     addUIToElement: function(force) {
51597         var me = this,
51598             array, cls;
51599
51600         me.callParent(arguments);
51601
51602         cls = me.baseCls + '-body-' + me.ui;
51603         if (!force && me.rendered) {
51604             if (me.bodyCls) {
51605                 me.body.addCls(me.bodyCls);
51606             } else {
51607                 me.body.addCls(cls);
51608             }
51609         } else {
51610             if (me.bodyCls) {
51611                 array = me.bodyCls.split(' ');
51612
51613                 if (!Ext.Array.contains(array, cls)) {
51614                     array.push(cls);
51615                 }
51616
51617                 me.bodyCls = array.join(' ');
51618             } else {
51619                 me.bodyCls = cls;
51620             }
51621         }
51622
51623         if (!force && me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
51624             me.titleCmp.textEl.addCls(me.baseCls + '-text-' + me.ui);
51625         }
51626     },
51627
51628     // inherit docs
51629     removeUIFromElement: function() {
51630         var me = this,
51631             array, cls;
51632
51633         me.callParent(arguments);
51634
51635         cls = me.baseCls + '-body-' + me.ui;
51636         if (me.rendered) {
51637             if (me.bodyCls) {
51638                 me.body.removeCls(me.bodyCls);
51639             } else {
51640                 me.body.removeCls(cls);
51641             }
51642         } else {
51643             if (me.bodyCls) {
51644                 array = me.bodyCls.split(' ');
51645                 Ext.Array.remove(array, cls);
51646                 me.bodyCls = array.join(' ');
51647             } else {
51648                 me.bodyCls = cls;
51649             }
51650         }
51651
51652         if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
51653             me.titleCmp.textEl.removeCls(me.baseCls + '-text-' + me.ui);
51654         }
51655     },
51656
51657     onClick: function(e) {
51658         if (!e.getTarget(Ext.baseCSSPrefix + 'tool')) {
51659             this.fireEvent('click', e);
51660         }
51661     },
51662
51663     getTargetEl: function() {
51664         return this.body || this.frameBody || this.el;
51665     },
51666
51667     /**
51668      * Sets the title of the header.
51669      * @param {String} title The title to be set
51670      */
51671     setTitle: function(title) {
51672         var me = this;
51673         if (me.rendered) {
51674             if (me.titleCmp.rendered) {
51675                 if (me.titleCmp.surface) {
51676                     me.title = title || '';
51677                     var sprite = me.titleCmp.surface.items.items[0],
51678                         surface = me.titleCmp.surface;
51679
51680                     surface.remove(sprite);
51681                     me.textConfig.type = 'text';
51682                     me.textConfig.text = title;
51683                     sprite = surface.add(me.textConfig);
51684                     sprite.setAttributes({
51685                         rotate: {
51686                             degrees: 90
51687                         }
51688                     }, true);
51689                     me.titleCmp.autoSizeSurface();
51690                 } else {
51691                     me.title = title || '&#160;';
51692                     me.titleCmp.textEl.update(me.title);
51693                 }
51694             } else {
51695                 me.titleCmp.on({
51696                     render: function() {
51697                         me.setTitle(title);
51698                     },
51699                     single: true
51700                 });
51701             }
51702         } else {
51703             me.on({
51704                 render: function() {
51705                     me.layout.layout();
51706                     me.setTitle(title);
51707                 },
51708                 single: true
51709             });
51710         }
51711     },
51712
51713     /**
51714      * Sets the CSS class that provides the icon image for this header.  This method will replace any existing
51715      * icon class if one has already been set.
51716      * @param {String} cls The new CSS class name
51717      */
51718     setIconCls: function(cls) {
51719         var me = this,
51720             isEmpty = !cls || !cls.length,
51721             iconCmp = me.iconCmp,
51722             el;
51723         
51724         me.iconCls = cls;
51725         if (!me.iconCmp && !isEmpty) {
51726             me.initIconCmp();
51727             me.insert(0, me.iconCmp);
51728         } else if (iconCmp) {
51729             if (isEmpty) {
51730                 me.iconCmp.destroy();
51731             } else {
51732                 el = iconCmp.iconEl;
51733                 el.removeCls(iconCmp.iconCls);
51734                 el.addCls(cls);
51735                 iconCmp.iconCls = cls;
51736             }
51737         }
51738     },
51739
51740     /**
51741      * Add a tool to the header
51742      * @param {Object} tool
51743      */
51744     addTool: function(tool) {
51745         this.tools.push(this.add(tool));
51746     },
51747
51748     /**
51749      * @private
51750      * Set up the tools.&lt;tool type> link in the owning Panel.
51751      * Bind the tool to its owning Panel.
51752      * @param component
51753      * @param index
51754      */
51755     onAdd: function(component, index) {
51756         this.callParent([arguments]);
51757         if (component instanceof Ext.panel.Tool) {
51758             component.bindTo(this.ownerCt);
51759             this.tools[component.type] = component;
51760         }
51761     }
51762 });
51763
51764 /**
51765  * @class Ext.fx.target.Element
51766  * @extends Ext.fx.target.Target
51767  * 
51768  * This class represents a animation target for an {@link Ext.Element}. In general this class will not be
51769  * created directly, the {@link Ext.Element} will be passed to the animation and
51770  * and the appropriate target will be created.
51771  */
51772 Ext.define('Ext.fx.target.Element', {
51773
51774     /* Begin Definitions */
51775     
51776     extend: 'Ext.fx.target.Target',
51777     
51778     /* End Definitions */
51779
51780     type: 'element',
51781
51782     getElVal: function(el, attr, val) {
51783         if (val == undefined) {
51784             if (attr === 'x') {
51785                 val = el.getX();
51786             }
51787             else if (attr === 'y') {
51788                 val = el.getY();
51789             }
51790             else if (attr === 'scrollTop') {
51791                 val = el.getScroll().top;
51792             }
51793             else if (attr === 'scrollLeft') {
51794                 val = el.getScroll().left;
51795             }
51796             else if (attr === 'height') {
51797                 val = el.getHeight();
51798             }
51799             else if (attr === 'width') {
51800                 val = el.getWidth();
51801             }
51802             else {
51803                 val = el.getStyle(attr);
51804             }
51805         }
51806         return val;
51807     },
51808
51809     getAttr: function(attr, val) {
51810         var el = this.target;
51811         return [[ el, this.getElVal(el, attr, val)]];
51812     },
51813
51814     setAttr: function(targetData) {
51815         var target = this.target,
51816             ln = targetData.length,
51817             attrs, attr, o, i, j, ln2, element, value;
51818         for (i = 0; i < ln; i++) {
51819             attrs = targetData[i].attrs;
51820             for (attr in attrs) {
51821                 if (attrs.hasOwnProperty(attr)) {
51822                     ln2 = attrs[attr].length;
51823                     for (j = 0; j < ln2; j++) {
51824                         o = attrs[attr][j];
51825                         element = o[0];
51826                         value = o[1];
51827                         if (attr === 'x') {
51828                             element.setX(value);
51829                         }
51830                         else if (attr === 'y') {
51831                             element.setY(value);
51832                         }
51833                         else if (attr === 'scrollTop') {
51834                             element.scrollTo('top', value);
51835                         }
51836                         else if (attr === 'scrollLeft') {
51837                             element.scrollTo('left',value);
51838                         }
51839                         else {
51840                             element.setStyle(attr, value);
51841                         }
51842                     }
51843                 }
51844             }
51845         }
51846     }
51847 });
51848
51849 /**
51850  * @class Ext.fx.target.CompositeElement
51851  * @extends Ext.fx.target.Element
51852  * 
51853  * This class represents a animation target for a {@link Ext.CompositeElement}. It allows
51854  * each {@link Ext.Element} in the group to be animated as a whole. In general this class will not be
51855  * created directly, the {@link Ext.CompositeElement} will be passed to the animation and
51856  * and the appropriate target will be created.
51857  */
51858 Ext.define('Ext.fx.target.CompositeElement', {
51859
51860     /* Begin Definitions */
51861
51862     extend: 'Ext.fx.target.Element',
51863
51864     /* End Definitions */
51865
51866     isComposite: true,
51867     
51868     constructor: function(target) {
51869         target.id = target.id || Ext.id(null, 'ext-composite-');
51870         this.callParent([target]);
51871     },
51872
51873     getAttr: function(attr, val) {
51874         var out = [],
51875             target = this.target;
51876         target.each(function(el) {
51877             out.push([el, this.getElVal(el, attr, val)]);
51878         }, this);
51879         return out;
51880     }
51881 });
51882
51883 /**
51884  * @class Ext.fx.Manager
51885  * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
51886  * @private
51887  * @singleton
51888  */
51889
51890 Ext.define('Ext.fx.Manager', {
51891
51892     /* Begin Definitions */
51893
51894     singleton: true,
51895
51896     requires: ['Ext.util.MixedCollection',
51897                'Ext.fx.target.Element',
51898                'Ext.fx.target.CompositeElement',
51899                'Ext.fx.target.Sprite',
51900                'Ext.fx.target.CompositeSprite',
51901                'Ext.fx.target.Component'],
51902
51903     mixins: {
51904         queue: 'Ext.fx.Queue'
51905     },
51906
51907     /* End Definitions */
51908
51909     constructor: function() {
51910         this.items = Ext.create('Ext.util.MixedCollection');
51911         this.mixins.queue.constructor.call(this);
51912
51913         // this.requestAnimFrame = (function() {
51914         //     var raf = window.requestAnimationFrame ||
51915         //               window.webkitRequestAnimationFrame ||
51916         //               window.mozRequestAnimationFrame ||
51917         //               window.oRequestAnimationFrame ||
51918         //               window.msRequestAnimationFrame;
51919         //     if (raf) {
51920         //         return function(callback, element) {
51921         //             raf(callback);
51922         //         };
51923         //     }
51924         //     else {
51925         //         return function(callback, element) {
51926         //             window.setTimeout(callback, Ext.fx.Manager.interval);
51927         //         };
51928         //     }
51929         // })();
51930     },
51931
51932     /**
51933      * @cfg {Number} interval Default interval in miliseconds to calculate each frame.  Defaults to 16ms (~60fps)
51934      */
51935     interval: 16,
51936
51937     /**
51938      * @cfg {Boolean} forceJS Turn off to not use CSS3 transitions when they are available
51939      */
51940     forceJS: true,
51941
51942     // @private Target factory
51943     createTarget: function(target) {
51944         var me = this,
51945             useCSS3 = !me.forceJS && Ext.supports.Transitions,
51946             targetObj;
51947
51948         me.useCSS3 = useCSS3;
51949
51950         // dom id
51951         if (Ext.isString(target)) {
51952             target = Ext.get(target);
51953         }
51954         // dom element
51955         if (target && target.tagName) {
51956             target = Ext.get(target);
51957             targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
51958             me.targets.add(targetObj);
51959             return targetObj;
51960         }
51961         if (Ext.isObject(target)) {
51962             // Element
51963             if (target.dom) {
51964                 targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
51965             }
51966             // Element Composite
51967             else if (target.isComposite) {
51968                 targetObj = Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target);
51969             }
51970             // Draw Sprite
51971             else if (target.isSprite) {
51972                 targetObj = Ext.create('Ext.fx.target.Sprite', target);
51973             }
51974             // Draw Sprite Composite
51975             else if (target.isCompositeSprite) {
51976                 targetObj = Ext.create('Ext.fx.target.CompositeSprite', target);
51977             }
51978             // Component
51979             else if (target.isComponent) {
51980                 targetObj = Ext.create('Ext.fx.target.Component', target);
51981             }
51982             else if (target.isAnimTarget) {
51983                 return target;
51984             }
51985             else {
51986                 return null;
51987             }
51988             me.targets.add(targetObj);
51989             return targetObj;
51990         }
51991         else {
51992             return null;
51993         }
51994     },
51995
51996     /**
51997      * Add an Anim to the manager. This is done automatically when an Anim instance is created.
51998      * @param {Ext.fx.Anim} anim
51999      */
52000     addAnim: function(anim) {
52001         var items = this.items,
52002             task = this.task;
52003         // var me = this,
52004         //     items = me.items,
52005         //     cb = function() {
52006         //         if (items.length) {
52007         //             me.task = true;
52008         //             me.runner();
52009         //             me.requestAnimFrame(cb);
52010         //         }
52011         //         else {
52012         //             me.task = false;
52013         //         }
52014         //     };
52015
52016         items.add(anim);
52017
52018         // Start the timer if not already running
52019         if (!task && items.length) {
52020             task = this.task = {
52021                 run: this.runner,
52022                 interval: this.interval,
52023                 scope: this
52024             };
52025             Ext.TaskManager.start(task);
52026         }
52027
52028         // //Start the timer if not already running
52029         // if (!me.task && items.length) {
52030         //     me.requestAnimFrame(cb);
52031         // }
52032     },
52033
52034     /**
52035      * Remove an Anim from the manager. This is done automatically when an Anim ends.
52036      * @param {Ext.fx.Anim} anim
52037      */
52038     removeAnim: function(anim) {
52039         // this.items.remove(anim);
52040         var items = this.items,
52041             task = this.task;
52042         items.remove(anim);
52043         // Stop the timer if there are no more managed Anims
52044         if (task && !items.length) {
52045             Ext.TaskManager.stop(task);
52046             delete this.task;
52047         }
52048     },
52049
52050     /**
52051      * @private
52052      * Filter function to determine which animations need to be started
52053      */
52054     startingFilter: function(o) {
52055         return o.paused === false && o.running === false && o.iterations > 0;
52056     },
52057
52058     /**
52059      * @private
52060      * Filter function to determine which animations are still running
52061      */
52062     runningFilter: function(o) {
52063         return o.paused === false && o.running === true && o.isAnimator !== true;
52064     },
52065
52066     /**
52067      * @private
52068      * Runner function being called each frame
52069      */
52070     runner: function() {
52071         var me = this,
52072             items = me.items;
52073
52074         me.targetData = {};
52075         me.targetArr = {};
52076
52077         // Single timestamp for all animations this interval
52078         me.timestamp = new Date();
52079
52080         // Start any items not current running
52081         items.filterBy(me.startingFilter).each(me.startAnim, me);
52082
52083         // Build the new attributes to be applied for all targets in this frame
52084         items.filterBy(me.runningFilter).each(me.runAnim, me);
52085
52086         // Apply all the pending changes to their targets
52087         me.applyPendingAttrs();
52088     },
52089
52090     /**
52091      * @private
52092      * Start the individual animation (initialization)
52093      */
52094     startAnim: function(anim) {
52095         anim.start(this.timestamp);
52096     },
52097
52098     /**
52099      * @private
52100      * Run the individual animation for this frame
52101      */
52102     runAnim: function(anim) {
52103         if (!anim) {
52104             return;
52105         }
52106         var me = this,
52107             targetId = anim.target.getId(),
52108             useCSS3 = me.useCSS3 && anim.target.type == 'element',
52109             elapsedTime = me.timestamp - anim.startTime,
52110             target, o;
52111
52112         this.collectTargetData(anim, elapsedTime, useCSS3);
52113
52114         // For CSS3 animation, we need to immediately set the first frame's attributes without any transition
52115         // to get a good initial state, then add the transition properties and set the final attributes.
52116         if (useCSS3) {
52117             // Flush the collected attributes, without transition
52118             anim.target.setAttr(me.targetData[targetId], true);
52119
52120             // Add the end frame data
52121             me.targetData[targetId] = [];
52122             me.collectTargetData(anim, anim.duration, useCSS3);
52123
52124             // Pause the animation so runAnim doesn't keep getting called
52125             anim.paused = true;
52126
52127             target = anim.target.target;
52128             // We only want to attach an event on the last element in a composite
52129             if (anim.target.isComposite) {
52130                 target = anim.target.target.last();
52131             }
52132
52133             // Listen for the transitionend event
52134             o = {};
52135             o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;
52136             o.scope = anim;
52137             o.single = true;
52138             target.on(o);
52139         }
52140         // For JS animation, trigger the lastFrame handler if this is the final frame
52141         else if (elapsedTime >= anim.duration) {
52142             me.applyPendingAttrs(true);
52143             delete me.targetData[targetId];
52144             delete me.targetArr[targetId];
52145             anim.lastFrame();
52146         }
52147     },
52148
52149     /**
52150      * Collect target attributes for the given Anim object at the given timestamp
52151      * @param {Ext.fx.Anim} anim The Anim instance
52152      * @param {Number} timestamp Time after the anim's start time
52153      */
52154     collectTargetData: function(anim, elapsedTime, useCSS3) {
52155         var targetId = anim.target.getId(),
52156             targetData = this.targetData[targetId],
52157             data;
52158         
52159         if (!targetData) {
52160             targetData = this.targetData[targetId] = [];
52161             this.targetArr[targetId] = anim.target;
52162         }
52163
52164         data = {
52165             duration: anim.duration,
52166             easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,
52167             attrs: {}
52168         };
52169         Ext.apply(data.attrs, anim.runAnim(elapsedTime));
52170         targetData.push(data);
52171     },
52172
52173     /**
52174      * @private
52175      * Apply all pending attribute changes to their targets
52176      */
52177     applyPendingAttrs: function(isLastFrame) {
52178         var targetData = this.targetData,
52179             targetArr = this.targetArr,
52180             targetId;
52181         for (targetId in targetData) {
52182             if (targetData.hasOwnProperty(targetId)) {
52183                 targetArr[targetId].setAttr(targetData[targetId], false, isLastFrame);
52184             }
52185         }
52186     }
52187 });
52188
52189 /**
52190  * @class Ext.fx.Animator
52191  *
52192  * This class is used to run keyframe based animations, which follows the CSS3 based animation structure.
52193  * Keyframe animations differ from typical from/to animations in that they offer the ability to specify values
52194  * at various points throughout the animation.
52195  *
52196  * ## Using Keyframes
52197  *
52198  * The {@link #keyframes} option is the most important part of specifying an animation when using this
52199  * class. A key frame is a point in a particular animation. We represent this as a percentage of the
52200  * total animation duration. At each key frame, we can specify the target values at that time. Note that
52201  * you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link #keyframe}
52202  * event that fires after each key frame is reached.
52203  *
52204  * ## Example
52205  *
52206  * In the example below, we modify the values of the element at each fifth throughout the animation.
52207  *
52208  *     @example
52209  *     Ext.create('Ext.fx.Animator', {
52210  *         target: Ext.getBody().createChild({
52211  *             style: {
52212  *                 width: '100px',
52213  *                 height: '100px',
52214  *                 'background-color': 'red'
52215  *             }
52216  *         }),
52217  *         duration: 10000, // 10 seconds
52218  *         keyframes: {
52219  *             0: {
52220  *                 opacity: 1,
52221  *                 backgroundColor: 'FF0000'
52222  *             },
52223  *             20: {
52224  *                 x: 30,
52225  *                 opacity: 0.5
52226  *             },
52227  *             40: {
52228  *                 x: 130,
52229  *                 backgroundColor: '0000FF'
52230  *             },
52231  *             60: {
52232  *                 y: 80,
52233  *                 opacity: 0.3
52234  *             },
52235  *             80: {
52236  *                 width: 200,
52237  *                 y: 200
52238  *             },
52239  *             100: {
52240  *                 opacity: 1,
52241  *                 backgroundColor: '00FF00'
52242  *             }
52243  *         }
52244  *     });
52245  */
52246 Ext.define('Ext.fx.Animator', {
52247
52248     /* Begin Definitions */
52249
52250     mixins: {
52251         observable: 'Ext.util.Observable'
52252     },
52253
52254     requires: ['Ext.fx.Manager'],
52255
52256     /* End Definitions */
52257
52258     isAnimator: true,
52259
52260     /**
52261      * @cfg {Number} duration
52262      * Time in milliseconds for the animation to last. Defaults to 250.
52263      */
52264     duration: 250,
52265
52266     /**
52267      * @cfg {Number} delay
52268      * Time to delay before starting the animation. Defaults to 0.
52269      */
52270     delay: 0,
52271
52272     /* private used to track a delayed starting time */
52273     delayStart: 0,
52274
52275     /**
52276      * @cfg {Boolean} dynamic
52277      * 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.
52278      */
52279     dynamic: false,
52280
52281     /**
52282      * @cfg {String} easing
52283      *
52284      * This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
52285      * speed over its duration.
52286      *
52287      *  - backIn
52288      *  - backOut
52289      *  - bounceIn
52290      *  - bounceOut
52291      *  - ease
52292      *  - easeIn
52293      *  - easeOut
52294      *  - easeInOut
52295      *  - elasticIn
52296      *  - elasticOut
52297      *  - cubic-bezier(x1, y1, x2, y2)
52298      *
52299      * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
52300      * specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
52301      * be in the range [0, 1] or the definition is invalid.
52302      *
52303      * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
52304      */
52305     easing: 'ease',
52306
52307     /**
52308      * Flag to determine if the animation has started
52309      * @property running
52310      * @type Boolean
52311      */
52312     running: false,
52313
52314     /**
52315      * Flag to determine if the animation is paused. Only set this to true if you need to
52316      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
52317      * @property paused
52318      * @type Boolean
52319      */
52320     paused: false,
52321
52322     /**
52323      * @private
52324      */
52325     damper: 1,
52326
52327     /**
52328      * @cfg {Number} iterations
52329      * Number of times to execute the animation. Defaults to 1.
52330      */
52331     iterations: 1,
52332
52333     /**
52334      * Current iteration the animation is running.
52335      * @property currentIteration
52336      * @type Number
52337      */
52338     currentIteration: 0,
52339
52340     /**
52341      * Current keyframe step of the animation.
52342      * @property keyframeStep
52343      * @type Number
52344      */
52345     keyframeStep: 0,
52346
52347     /**
52348      * @private
52349      */
52350     animKeyFramesRE: /^(from|to|\d+%?)$/,
52351
52352     /**
52353      * @cfg {Ext.fx.target.Target} target
52354      * The Ext.fx.target to apply the animation to.  If not specified during initialization, this can be passed to the applyAnimator
52355      * method to apply the same animation to many targets.
52356      */
52357
52358      /**
52359       * @cfg {Object} keyframes
52360       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
52361       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
52362       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
52363       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
52364       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
52365  <pre><code>
52366 keyframes : {
52367     '0%': {
52368         left: 100
52369     },
52370     '40%': {
52371         left: 150
52372     },
52373     '60%': {
52374         left: 75
52375     },
52376     '100%': {
52377         left: 100
52378     }
52379 }
52380  </code></pre>
52381       */
52382     constructor: function(config) {
52383         var me = this;
52384         config = Ext.apply(me, config || {});
52385         me.config = config;
52386         me.id = Ext.id(null, 'ext-animator-');
52387         me.addEvents(
52388             /**
52389              * @event beforeanimate
52390              * Fires before the animation starts. A handler can return false to cancel the animation.
52391              * @param {Ext.fx.Animator} this
52392              */
52393             'beforeanimate',
52394             /**
52395               * @event keyframe
52396               * Fires at each keyframe.
52397               * @param {Ext.fx.Animator} this
52398               * @param {Number} keyframe step number
52399               */
52400             'keyframe',
52401             /**
52402              * @event afteranimate
52403              * Fires when the animation is complete.
52404              * @param {Ext.fx.Animator} this
52405              * @param {Date} startTime
52406              */
52407             'afteranimate'
52408         );
52409         me.mixins.observable.constructor.call(me, config);
52410         me.timeline = [];
52411         me.createTimeline(me.keyframes);
52412         if (me.target) {
52413             me.applyAnimator(me.target);
52414             Ext.fx.Manager.addAnim(me);
52415         }
52416     },
52417
52418     /**
52419      * @private
52420      */
52421     sorter: function (a, b) {
52422         return a.pct - b.pct;
52423     },
52424
52425     /**
52426      * @private
52427      * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
52428      * or applying the 'to' configuration to all keyframes.  Also calculates the proper animation duration per keyframe.
52429      */
52430     createTimeline: function(keyframes) {
52431         var me = this,
52432             attrs = [],
52433             to = me.to || {},
52434             duration = me.duration,
52435             prevMs, ms, i, ln, pct, anim, nextAnim, attr;
52436
52437         for (pct in keyframes) {
52438             if (keyframes.hasOwnProperty(pct) && me.animKeyFramesRE.test(pct)) {
52439                 attr = {attrs: Ext.apply(keyframes[pct], to)};
52440                 // CSS3 spec allow for from/to to be specified.
52441                 if (pct == "from") {
52442                     pct = 0;
52443                 }
52444                 else if (pct == "to") {
52445                     pct = 100;
52446                 }
52447                 // convert % values into integers
52448                 attr.pct = parseInt(pct, 10);
52449                 attrs.push(attr);
52450             }
52451         }
52452         // Sort by pct property
52453         Ext.Array.sort(attrs, me.sorter);
52454         // Only an end
52455         //if (attrs[0].pct) {
52456         //    attrs.unshift({pct: 0, attrs: element.attrs});
52457         //}
52458
52459         ln = attrs.length;
52460         for (i = 0; i < ln; i++) {
52461             prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
52462             ms = duration * (attrs[i].pct / 100);
52463             me.timeline.push({
52464                 duration: ms - prevMs,
52465                 attrs: attrs[i].attrs
52466             });
52467         }
52468     },
52469
52470     /**
52471      * Applies animation to the Ext.fx.target
52472      * @private
52473      * @param target
52474      * @type String/Object
52475      */
52476     applyAnimator: function(target) {
52477         var me = this,
52478             anims = [],
52479             timeline = me.timeline,
52480             reverse = me.reverse,
52481             ln = timeline.length,
52482             anim, easing, damper, initial, attrs, lastAttrs, i;
52483
52484         if (me.fireEvent('beforeanimate', me) !== false) {
52485             for (i = 0; i < ln; i++) {
52486                 anim = timeline[i];
52487                 attrs = anim.attrs;
52488                 easing = attrs.easing || me.easing;
52489                 damper = attrs.damper || me.damper;
52490                 delete attrs.easing;
52491                 delete attrs.damper;
52492                 anim = Ext.create('Ext.fx.Anim', {
52493                     target: target,
52494                     easing: easing,
52495                     damper: damper,
52496                     duration: anim.duration,
52497                     paused: true,
52498                     to: attrs
52499                 });
52500                 anims.push(anim);
52501             }
52502             me.animations = anims;
52503             me.target = anim.target;
52504             for (i = 0; i < ln - 1; i++) {
52505                 anim = anims[i];
52506                 anim.nextAnim = anims[i + 1];
52507                 anim.on('afteranimate', function() {
52508                     this.nextAnim.paused = false;
52509                 });
52510                 anim.on('afteranimate', function() {
52511                     this.fireEvent('keyframe', this, ++this.keyframeStep);
52512                 }, me);
52513             }
52514             anims[ln - 1].on('afteranimate', function() {
52515                 this.lastFrame();
52516             }, me);
52517         }
52518     },
52519
52520     /**
52521      * @private
52522      * Fires beforeanimate and sets the running flag.
52523      */
52524     start: function(startTime) {
52525         var me = this,
52526             delay = me.delay,
52527             delayStart = me.delayStart,
52528             delayDelta;
52529         if (delay) {
52530             if (!delayStart) {
52531                 me.delayStart = startTime;
52532                 return;
52533             }
52534             else {
52535                 delayDelta = startTime - delayStart;
52536                 if (delayDelta < delay) {
52537                     return;
52538                 }
52539                 else {
52540                     // Compensate for frame delay;
52541                     startTime = new Date(delayStart.getTime() + delay);
52542                 }
52543             }
52544         }
52545         if (me.fireEvent('beforeanimate', me) !== false) {
52546             me.startTime = startTime;
52547             me.running = true;
52548             me.animations[me.keyframeStep].paused = false;
52549         }
52550     },
52551
52552     /**
52553      * @private
52554      * Perform lastFrame cleanup and handle iterations
52555      * @returns a hash of the new attributes.
52556      */
52557     lastFrame: function() {
52558         var me = this,
52559             iter = me.iterations,
52560             iterCount = me.currentIteration;
52561
52562         iterCount++;
52563         if (iterCount < iter) {
52564             me.startTime = new Date();
52565             me.currentIteration = iterCount;
52566             me.keyframeStep = 0;
52567             me.applyAnimator(me.target);
52568             me.animations[me.keyframeStep].paused = false;
52569         }
52570         else {
52571             me.currentIteration = 0;
52572             me.end();
52573         }
52574     },
52575
52576     /**
52577      * Fire afteranimate event and end the animation. Usually called automatically when the
52578      * animation reaches its final frame, but can also be called manually to pre-emptively
52579      * stop and destroy the running animation.
52580      */
52581     end: function() {
52582         var me = this;
52583         me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
52584     }
52585 });
52586 /**
52587  * @class Ext.fx.Easing
52588  *
52589  * This class contains a series of function definitions used to modify values during an animation.
52590  * They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
52591  * speed over its duration. The following options are available: 
52592  *
52593  * - linear The default easing type
52594  * - backIn
52595  * - backOut
52596  * - bounceIn
52597  * - bounceOut
52598  * - ease
52599  * - easeIn
52600  * - easeOut
52601  * - easeInOut
52602  * - elasticIn
52603  * - elasticOut
52604  * - cubic-bezier(x1, y1, x2, y2)
52605  *
52606  * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
52607  * specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
52608  * be in the range [0, 1] or the definition is invalid.
52609  *
52610  * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
52611  *
52612  * @singleton
52613  */
52614 Ext.ns('Ext.fx');
52615
52616 Ext.require('Ext.fx.CubicBezier', function() {
52617     var math = Math,
52618         pi = math.PI,
52619         pow = math.pow,
52620         sin = math.sin,
52621         sqrt = math.sqrt,
52622         abs = math.abs,
52623         backInSeed = 1.70158;
52624     Ext.fx.Easing = {
52625         // ease: Ext.fx.CubicBezier.cubicBezier(0.25, 0.1, 0.25, 1),
52626         // linear: Ext.fx.CubicBezier.cubicBezier(0, 0, 1, 1),
52627         // 'ease-in': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
52628         // 'ease-out': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
52629         // 'ease-in-out': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1),
52630         // 'easeIn': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
52631         // 'easeOut': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
52632         // 'easeInOut': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1)
52633     };
52634
52635     Ext.apply(Ext.fx.Easing, {
52636         linear: function(n) {
52637             return n;
52638         },
52639         ease: function(n) {
52640             var q = 0.07813 - n / 2,
52641                 alpha = -0.25,
52642                 Q = sqrt(0.0066 + q * q),
52643                 x = Q - q,
52644                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
52645                 y = -Q - q,
52646                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
52647                 t = X + Y + 0.25;
52648             return pow(1 - t, 2) * 3 * t * 0.1 + (1 - t) * 3 * t * t + t * t * t;
52649         },
52650         easeIn: function (n) {
52651             return pow(n, 1.7);
52652         },
52653         easeOut: function (n) {
52654             return pow(n, 0.48);
52655         },
52656         easeInOut: function(n) {
52657             var q = 0.48 - n / 1.04,
52658                 Q = sqrt(0.1734 + q * q),
52659                 x = Q - q,
52660                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
52661                 y = -Q - q,
52662                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
52663                 t = X + Y + 0.5;
52664             return (1 - t) * 3 * t * t + t * t * t;
52665         },
52666         backIn: function (n) {
52667             return n * n * ((backInSeed + 1) * n - backInSeed);
52668         },
52669         backOut: function (n) {
52670             n = n - 1;
52671             return n * n * ((backInSeed + 1) * n + backInSeed) + 1;
52672         },
52673         elasticIn: function (n) {
52674             if (n === 0 || n === 1) {
52675                 return n;
52676             }
52677             var p = 0.3,
52678                 s = p / 4;
52679             return pow(2, -10 * n) * sin((n - s) * (2 * pi) / p) + 1;
52680         },
52681         elasticOut: function (n) {
52682             return 1 - Ext.fx.Easing.elasticIn(1 - n);
52683         },
52684         bounceIn: function (n) {
52685             return 1 - Ext.fx.Easing.bounceOut(1 - n);
52686         },
52687         bounceOut: function (n) {
52688             var s = 7.5625,
52689                 p = 2.75,
52690                 l;
52691             if (n < (1 / p)) {
52692                 l = s * n * n;
52693             } else {
52694                 if (n < (2 / p)) {
52695                     n -= (1.5 / p);
52696                     l = s * n * n + 0.75;
52697                 } else {
52698                     if (n < (2.5 / p)) {
52699                         n -= (2.25 / p);
52700                         l = s * n * n + 0.9375;
52701                     } else {
52702                         n -= (2.625 / p);
52703                         l = s * n * n + 0.984375;
52704                     }
52705                 }
52706             }
52707             return l;
52708         }
52709     });
52710     Ext.apply(Ext.fx.Easing, {
52711         'back-in': Ext.fx.Easing.backIn,
52712         'back-out': Ext.fx.Easing.backOut,
52713         'ease-in': Ext.fx.Easing.easeIn,
52714         'ease-out': Ext.fx.Easing.easeOut,
52715         'elastic-in': Ext.fx.Easing.elasticIn,
52716         'elastic-out': Ext.fx.Easing.elasticIn,
52717         'bounce-in': Ext.fx.Easing.bounceIn,
52718         'bounce-out': Ext.fx.Easing.bounceOut,
52719         'ease-in-out': Ext.fx.Easing.easeInOut
52720     });
52721 });
52722 /**
52723  * @class Ext.draw.Draw
52724  * Base Drawing class.  Provides base drawing functions.
52725  * @private
52726  */
52727 Ext.define('Ext.draw.Draw', {
52728     /* Begin Definitions */
52729
52730     singleton: true,
52731
52732     requires: ['Ext.draw.Color'],
52733
52734     /* End Definitions */
52735
52736     pathToStringRE: /,?([achlmqrstvxz]),?/gi,
52737     pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
52738     pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
52739     stopsRE: /^(\d+%?)$/,
52740     radian: Math.PI / 180,
52741
52742     availableAnimAttrs: {
52743         along: "along",
52744         blur: null,
52745         "clip-rect": "csv",
52746         cx: null,
52747         cy: null,
52748         fill: "color",
52749         "fill-opacity": null,
52750         "font-size": null,
52751         height: null,
52752         opacity: null,
52753         path: "path",
52754         r: null,
52755         rotation: "csv",
52756         rx: null,
52757         ry: null,
52758         scale: "csv",
52759         stroke: "color",
52760         "stroke-opacity": null,
52761         "stroke-width": null,
52762         translation: "csv",
52763         width: null,
52764         x: null,
52765         y: null
52766     },
52767
52768     is: function(o, type) {
52769         type = String(type).toLowerCase();
52770         return (type == "object" && o === Object(o)) ||
52771             (type == "undefined" && typeof o == type) ||
52772             (type == "null" && o === null) ||
52773             (type == "array" && Array.isArray && Array.isArray(o)) ||
52774             (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
52775     },
52776
52777     ellipsePath: function(sprite) {
52778         var attr = sprite.attr;
52779         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);
52780     },
52781
52782     rectPath: function(sprite) {
52783         var attr = sprite.attr;
52784         if (attr.radius) {
52785             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);
52786         }
52787         else {
52788             return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
52789         }
52790     },
52791
52792     // To be deprecated, converts itself (an arrayPath) to a proper SVG path string
52793     path2string: function () {
52794         return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
52795     },
52796
52797     // Convert the passed arrayPath to a proper SVG path string (d attribute)
52798     pathToString: function(arrayPath) {
52799         return arrayPath.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
52800     },
52801
52802     parsePathString: function (pathString) {
52803         if (!pathString) {
52804             return null;
52805         }
52806         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
52807             data = [],
52808             me = this;
52809         if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
52810             data = me.pathClone(pathString);
52811         }
52812         if (!data.length) {
52813             String(pathString).replace(me.pathCommandRE, function (a, b, c) {
52814                 var params = [],
52815                     name = b.toLowerCase();
52816                 c.replace(me.pathValuesRE, function (a, b) {
52817                     b && params.push(+b);
52818                 });
52819                 if (name == "m" && params.length > 2) {
52820                     data.push([b].concat(Ext.Array.splice(params, 0, 2)));
52821                     name = "l";
52822                     b = (b == "m") ? "l" : "L";
52823                 }
52824                 while (params.length >= paramCounts[name]) {
52825                     data.push([b].concat(Ext.Array.splice(params, 0, paramCounts[name])));
52826                     if (!paramCounts[name]) {
52827                         break;
52828                     }
52829                 }
52830             });
52831         }
52832         data.toString = me.path2string;
52833         return data;
52834     },
52835
52836     mapPath: function (path, matrix) {
52837         if (!matrix) {
52838             return path;
52839         }
52840         var x, y, i, ii, j, jj, pathi;
52841         path = this.path2curve(path);
52842         for (i = 0, ii = path.length; i < ii; i++) {
52843             pathi = path[i];
52844             for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
52845                 x = matrix.x(pathi[j], pathi[j + 1]);
52846                 y = matrix.y(pathi[j], pathi[j + 1]);
52847                 pathi[j] = x;
52848                 pathi[j + 1] = y;
52849             }
52850         }
52851         return path;
52852     },
52853
52854     pathClone: function(pathArray) {
52855         var res = [],
52856             j, jj, i, ii;
52857         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
52858             pathArray = this.parsePathString(pathArray);
52859         }
52860         for (i = 0, ii = pathArray.length; i < ii; i++) {
52861             res[i] = [];
52862             for (j = 0, jj = pathArray[i].length; j < jj; j++) {
52863                 res[i][j] = pathArray[i][j];
52864             }
52865         }
52866         res.toString = this.path2string;
52867         return res;
52868     },
52869
52870     pathToAbsolute: function (pathArray) {
52871         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
52872             pathArray = this.parsePathString(pathArray);
52873         }
52874         var res = [],
52875             x = 0,
52876             y = 0,
52877             mx = 0,
52878             my = 0,
52879             i = 0,
52880             ln = pathArray.length,
52881             r, pathSegment, j, ln2;
52882         // MoveTo initial x/y position
52883         if (ln && pathArray[0][0] == "M") {
52884             x = +pathArray[0][1];
52885             y = +pathArray[0][2];
52886             mx = x;
52887             my = y;
52888             i++;
52889             res[0] = ["M", x, y];
52890         }
52891         for (; i < ln; i++) {
52892             r = res[i] = [];
52893             pathSegment = pathArray[i];
52894             if (pathSegment[0] != pathSegment[0].toUpperCase()) {
52895                 r[0] = pathSegment[0].toUpperCase();
52896                 switch (r[0]) {
52897                     // Elliptical Arc
52898                     case "A":
52899                         r[1] = pathSegment[1];
52900                         r[2] = pathSegment[2];
52901                         r[3] = pathSegment[3];
52902                         r[4] = pathSegment[4];
52903                         r[5] = pathSegment[5];
52904                         r[6] = +(pathSegment[6] + x);
52905                         r[7] = +(pathSegment[7] + y);
52906                         break;
52907                     // Vertical LineTo
52908                     case "V":
52909                         r[1] = +pathSegment[1] + y;
52910                         break;
52911                     // Horizontal LineTo
52912                     case "H":
52913                         r[1] = +pathSegment[1] + x;
52914                         break;
52915                     case "M":
52916                     // MoveTo
52917                         mx = +pathSegment[1] + x;
52918                         my = +pathSegment[2] + y;
52919                     default:
52920                         j = 1;
52921                         ln2 = pathSegment.length;
52922                         for (; j < ln2; j++) {
52923                             r[j] = +pathSegment[j] + ((j % 2) ? x : y);
52924                         }
52925                 }
52926             }
52927             else {
52928                 j = 0;
52929                 ln2 = pathSegment.length;
52930                 for (; j < ln2; j++) {
52931                     res[i][j] = pathSegment[j];
52932                 }
52933             }
52934             switch (r[0]) {
52935                 // ClosePath
52936                 case "Z":
52937                     x = mx;
52938                     y = my;
52939                     break;
52940                 // Horizontal LineTo
52941                 case "H":
52942                     x = r[1];
52943                     break;
52944                 // Vertical LineTo
52945                 case "V":
52946                     y = r[1];
52947                     break;
52948                 // MoveTo
52949                 case "M":
52950                     pathSegment = res[i];
52951                     ln2 = pathSegment.length;
52952                     mx = pathSegment[ln2 - 2];
52953                     my = pathSegment[ln2 - 1];
52954                 default:
52955                     pathSegment = res[i];
52956                     ln2 = pathSegment.length;
52957                     x = pathSegment[ln2 - 2];
52958                     y = pathSegment[ln2 - 1];
52959             }
52960         }
52961         res.toString = this.path2string;
52962         return res;
52963     },
52964
52965     // TO BE DEPRECATED
52966     pathToRelative: function (pathArray) {
52967         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
52968             pathArray = this.parsePathString(pathArray);
52969         }
52970         var res = [],
52971             x = 0,
52972             y = 0,
52973             mx = 0,
52974             my = 0,
52975             start = 0;
52976         if (pathArray[0][0] == "M") {
52977             x = pathArray[0][1];
52978             y = pathArray[0][2];
52979             mx = x;
52980             my = y;
52981             start++;
52982             res.push(["M", x, y]);
52983         }
52984         for (var i = start, ii = pathArray.length; i < ii; i++) {
52985             var r = res[i] = [],
52986                 pa = pathArray[i];
52987             if (pa[0] != pa[0].toLowerCase()) {
52988                 r[0] = pa[0].toLowerCase();
52989                 switch (r[0]) {
52990                     case "a":
52991                         r[1] = pa[1];
52992                         r[2] = pa[2];
52993                         r[3] = pa[3];
52994                         r[4] = pa[4];
52995                         r[5] = pa[5];
52996                         r[6] = +(pa[6] - x).toFixed(3);
52997                         r[7] = +(pa[7] - y).toFixed(3);
52998                         break;
52999                     case "v":
53000                         r[1] = +(pa[1] - y).toFixed(3);
53001                         break;
53002                     case "m":
53003                         mx = pa[1];
53004                         my = pa[2];
53005                     default:
53006                         for (var j = 1, jj = pa.length; j < jj; j++) {
53007                             r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
53008                         }
53009                 }
53010             } else {
53011                 r = res[i] = [];
53012                 if (pa[0] == "m") {
53013                     mx = pa[1] + x;
53014                     my = pa[2] + y;
53015                 }
53016                 for (var k = 0, kk = pa.length; k < kk; k++) {
53017                     res[i][k] = pa[k];
53018                 }
53019             }
53020             var len = res[i].length;
53021             switch (res[i][0]) {
53022                 case "z":
53023                     x = mx;
53024                     y = my;
53025                     break;
53026                 case "h":
53027                     x += +res[i][len - 1];
53028                     break;
53029                 case "v":
53030                     y += +res[i][len - 1];
53031                     break;
53032                 default:
53033                     x += +res[i][len - 2];
53034                     y += +res[i][len - 1];
53035             }
53036         }
53037         res.toString = this.path2string;
53038         return res;
53039     },
53040
53041     // Returns a path converted to a set of curveto commands
53042     path2curve: function (path) {
53043         var me = this,
53044             points = me.pathToAbsolute(path),
53045             ln = points.length,
53046             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
53047             i, seg, segLn, point;
53048             
53049         for (i = 0; i < ln; i++) {
53050             points[i] = me.command2curve(points[i], attrs);
53051             if (points[i].length > 7) {
53052                     points[i].shift();
53053                     point = points[i];
53054                     while (point.length) {
53055                         Ext.Array.splice(points, i++, 0, ["C"].concat(Ext.Array.splice(point, 0, 6)));
53056                     }
53057                     Ext.Array.erase(points, i, 1);
53058                     ln = points.length;
53059                 }
53060             seg = points[i];
53061             segLn = seg.length;
53062             attrs.x = seg[segLn - 2];
53063             attrs.y = seg[segLn - 1];
53064             attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
53065             attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
53066         }
53067         return points;
53068     },
53069     
53070     interpolatePaths: function (path, path2) {
53071         var me = this,
53072             p = me.pathToAbsolute(path),
53073             p2 = me.pathToAbsolute(path2),
53074             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
53075             attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
53076             fixArc = function (pp, i) {
53077                 if (pp[i].length > 7) {
53078                     pp[i].shift();
53079                     var pi = pp[i];
53080                     while (pi.length) {
53081                         Ext.Array.splice(pp, i++, 0, ["C"].concat(Ext.Array.splice(pi, 0, 6)));
53082                     }
53083                     Ext.Array.erase(pp, i, 1);
53084                     ii = Math.max(p.length, p2.length || 0);
53085                 }
53086             },
53087             fixM = function (path1, path2, a1, a2, i) {
53088                 if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
53089                     Ext.Array.splice(path2, i, 0, ["M", a2.x, a2.y]);
53090                     a1.bx = 0;
53091                     a1.by = 0;
53092                     a1.x = path1[i][1];
53093                     a1.y = path1[i][2];
53094                     ii = Math.max(p.length, p2.length || 0);
53095                 }
53096             };
53097         for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
53098             p[i] = me.command2curve(p[i], attrs);
53099             fixArc(p, i);
53100             (p2[i] = me.command2curve(p2[i], attrs2));
53101             fixArc(p2, i);
53102             fixM(p, p2, attrs, attrs2, i);
53103             fixM(p2, p, attrs2, attrs, i);
53104             var seg = p[i],
53105                 seg2 = p2[i],
53106                 seglen = seg.length,
53107                 seg2len = seg2.length;
53108             attrs.x = seg[seglen - 2];
53109             attrs.y = seg[seglen - 1];
53110             attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
53111             attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
53112             attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
53113             attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
53114             attrs2.x = seg2[seg2len - 2];
53115             attrs2.y = seg2[seg2len - 1];
53116         }
53117         return [p, p2];
53118     },
53119     
53120     //Returns any path command as a curveto command based on the attrs passed
53121     command2curve: function (pathCommand, d) {
53122         var me = this;
53123         if (!pathCommand) {
53124             return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
53125         }
53126         if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
53127             d.qx = d.qy = null;
53128         }
53129         switch (pathCommand[0]) {
53130             case "M":
53131                 d.X = pathCommand[1];
53132                 d.Y = pathCommand[2];
53133                 break;
53134             case "A":
53135                 pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
53136                 break;
53137             case "S":
53138                 pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
53139                 break;
53140             case "T":
53141                 d.qx = d.x + (d.x - (d.qx || d.x));
53142                 d.qy = d.y + (d.y - (d.qy || d.y));
53143                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
53144                 break;
53145             case "Q":
53146                 d.qx = pathCommand[1];
53147                 d.qy = pathCommand[2];
53148                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
53149                 break;
53150             case "L":
53151                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
53152                 break;
53153             case "H":
53154                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
53155                 break;
53156             case "V":
53157                 pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
53158                 break;
53159             case "Z":
53160                 pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
53161                 break;
53162         }
53163         return pathCommand;
53164     },
53165
53166     quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
53167         var _13 = 1 / 3,
53168             _23 = 2 / 3;
53169         return [
53170                 _13 * x1 + _23 * ax,
53171                 _13 * y1 + _23 * ay,
53172                 _13 * x2 + _23 * ax,
53173                 _13 * y2 + _23 * ay,
53174                 x2,
53175                 y2
53176             ];
53177     },
53178     
53179     rotate: function (x, y, rad) {
53180         var cos = Math.cos(rad),
53181             sin = Math.sin(rad),
53182             X = x * cos - y * sin,
53183             Y = x * sin + y * cos;
53184         return {x: X, y: Y};
53185     },
53186
53187     arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
53188         // for more information of where this Math came from visit:
53189         // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
53190         var me = this,
53191             PI = Math.PI,
53192             radian = me.radian,
53193             _120 = PI * 120 / 180,
53194             rad = radian * (+angle || 0),
53195             res = [],
53196             math = Math,
53197             mcos = math.cos,
53198             msin = math.sin,
53199             msqrt = math.sqrt,
53200             mabs = math.abs,
53201             masin = math.asin,
53202             xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
53203             t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
53204         if (!recursive) {
53205             xy = me.rotate(x1, y1, -rad);
53206             x1 = xy.x;
53207             y1 = xy.y;
53208             xy = me.rotate(x2, y2, -rad);
53209             x2 = xy.x;
53210             y2 = xy.y;
53211             cos = mcos(radian * angle);
53212             sin = msin(radian * angle);
53213             x = (x1 - x2) / 2;
53214             y = (y1 - y2) / 2;
53215             h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
53216             if (h > 1) {
53217                 h = msqrt(h);
53218                 rx = h * rx;
53219                 ry = h * ry;
53220             }
53221             rx2 = rx * rx;
53222             ry2 = ry * ry;
53223             k = (large_arc_flag == sweep_flag ? -1 : 1) *
53224                     msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
53225             cx = k * rx * y / ry + (x1 + x2) / 2;
53226             cy = k * -ry * x / rx + (y1 + y2) / 2;
53227             f1 = masin(((y1 - cy) / ry).toFixed(7));
53228             f2 = masin(((y2 - cy) / ry).toFixed(7));
53229
53230             f1 = x1 < cx ? PI - f1 : f1;
53231             f2 = x2 < cx ? PI - f2 : f2;
53232             if (f1 < 0) {
53233                 f1 = PI * 2 + f1;
53234             }
53235             if (f2 < 0) {
53236                 f2 = PI * 2 + f2;
53237             }
53238             if (sweep_flag && f1 > f2) {
53239                 f1 = f1 - PI * 2;
53240             }
53241             if (!sweep_flag && f2 > f1) {
53242                 f2 = f2 - PI * 2;
53243             }
53244         }
53245         else {
53246             f1 = recursive[0];
53247             f2 = recursive[1];
53248             cx = recursive[2];
53249             cy = recursive[3];
53250         }
53251         df = f2 - f1;
53252         if (mabs(df) > _120) {
53253             f2old = f2;
53254             x2old = x2;
53255             y2old = y2;
53256             f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
53257             x2 = cx + rx * mcos(f2);
53258             y2 = cy + ry * msin(f2);
53259             res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
53260         }
53261         df = f2 - f1;
53262         c1 = mcos(f1);
53263         s1 = msin(f1);
53264         c2 = mcos(f2);
53265         s2 = msin(f2);
53266         t = math.tan(df / 4);
53267         hx = 4 / 3 * rx * t;
53268         hy = 4 / 3 * ry * t;
53269         m1 = [x1, y1];
53270         m2 = [x1 + hx * s1, y1 - hy * c1];
53271         m3 = [x2 + hx * s2, y2 - hy * c2];
53272         m4 = [x2, y2];
53273         m2[0] = 2 * m1[0] - m2[0];
53274         m2[1] = 2 * m1[1] - m2[1];
53275         if (recursive) {
53276             return [m2, m3, m4].concat(res);
53277         }
53278         else {
53279             res = [m2, m3, m4].concat(res).join().split(",");
53280             newres = [];
53281             ln = res.length;
53282             for (i = 0;  i < ln; i++) {
53283                 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
53284             }
53285             return newres;
53286         }
53287     },
53288
53289     // TO BE DEPRECATED
53290     rotateAndTranslatePath: function (sprite) {
53291         var alpha = sprite.rotation.degrees,
53292             cx = sprite.rotation.x,
53293             cy = sprite.rotation.y,
53294             dx = sprite.translation.x,
53295             dy = sprite.translation.y,
53296             path,
53297             i,
53298             p,
53299             xy,
53300             j,
53301             res = [];
53302         if (!alpha && !dx && !dy) {
53303             return this.pathToAbsolute(sprite.attr.path);
53304         }
53305         dx = dx || 0;
53306         dy = dy || 0;
53307         path = this.pathToAbsolute(sprite.attr.path);
53308         for (i = path.length; i--;) {
53309             p = res[i] = path[i].slice();
53310             if (p[0] == "A") {
53311                 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
53312                 p[6] = xy.x + dx;
53313                 p[7] = xy.y + dy;
53314             } else {
53315                 j = 1;
53316                 while (p[j + 1] != null) {
53317                     xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
53318                     p[j] = xy.x + dx;
53319                     p[j + 1] = xy.y + dy;
53320                     j += 2;
53321                 }
53322             }
53323         }
53324         return res;
53325     },
53326
53327     // TO BE DEPRECATED
53328     rotatePoint: function (x, y, alpha, cx, cy) {
53329         if (!alpha) {
53330             return {
53331                 x: x,
53332                 y: y
53333             };
53334         }
53335         cx = cx || 0;
53336         cy = cy || 0;
53337         x = x - cx;
53338         y = y - cy;
53339         alpha = alpha * this.radian;
53340         var cos = Math.cos(alpha),
53341             sin = Math.sin(alpha);
53342         return {
53343             x: x * cos - y * sin + cx,
53344             y: x * sin + y * cos + cy
53345         };
53346     },
53347
53348     pathDimensions: function (path) {
53349         if (!path || !(path + "")) {
53350             return {x: 0, y: 0, width: 0, height: 0};
53351         }
53352         path = this.path2curve(path);
53353         var x = 0, 
53354             y = 0,
53355             X = [],
53356             Y = [],
53357             i = 0,
53358             ln = path.length,
53359             p, xmin, ymin, dim;
53360         for (; i < ln; i++) {
53361             p = path[i];
53362             if (p[0] == "M") {
53363                 x = p[1];
53364                 y = p[2];
53365                 X.push(x);
53366                 Y.push(y);
53367             }
53368             else {
53369                 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
53370                 X = X.concat(dim.min.x, dim.max.x);
53371                 Y = Y.concat(dim.min.y, dim.max.y);
53372                 x = p[5];
53373                 y = p[6];
53374             }
53375         }
53376         xmin = Math.min.apply(0, X);
53377         ymin = Math.min.apply(0, Y);
53378         return {
53379             x: xmin,
53380             y: ymin,
53381             path: path,
53382             width: Math.max.apply(0, X) - xmin,
53383             height: Math.max.apply(0, Y) - ymin
53384         };
53385     },
53386
53387     intersectInside: function(path, cp1, cp2) {
53388         return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) > (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
53389     },
53390
53391     intersectIntersection: function(s, e, cp1, cp2) {
53392         var p = [],
53393             dcx = cp1[0] - cp2[0],
53394             dcy = cp1[1] - cp2[1],
53395             dpx = s[0] - e[0],
53396             dpy = s[1] - e[1],
53397             n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
53398             n2 = s[0] * e[1] - s[1] * e[0],
53399             n3 = 1 / (dcx * dpy - dcy * dpx);
53400
53401         p[0] = (n1 * dpx - n2 * dcx) * n3;
53402         p[1] = (n1 * dpy - n2 * dcy) * n3;
53403         return p;
53404     },
53405
53406     intersect: function(subjectPolygon, clipPolygon) {
53407         var me = this,
53408             i = 0,
53409             ln = clipPolygon.length,
53410             cp1 = clipPolygon[ln - 1],
53411             outputList = subjectPolygon,
53412             cp2, s, e, point, ln2, inputList, j;
53413         for (; i < ln; ++i) {
53414             cp2 = clipPolygon[i];
53415             inputList = outputList;
53416             outputList = [];
53417             s = inputList[inputList.length - 1];
53418             j = 0;
53419             ln2 = inputList.length;
53420             for (; j < ln2; j++) {
53421                 e = inputList[j];
53422                 if (me.intersectInside(e, cp1, cp2)) {
53423                     if (!me.intersectInside(s, cp1, cp2)) {
53424                         outputList.push(me.intersectIntersection(s, e, cp1, cp2));
53425                     }
53426                     outputList.push(e);
53427                 }
53428                 else if (me.intersectInside(s, cp1, cp2)) {
53429                     outputList.push(me.intersectIntersection(s, e, cp1, cp2));
53430                 }
53431                 s = e;
53432             }
53433             cp1 = cp2;
53434         }
53435         return outputList;
53436     },
53437
53438     curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
53439         var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
53440             b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
53441             c = p1x - c1x,
53442             t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
53443             t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
53444             y = [p1y, p2y],
53445             x = [p1x, p2x],
53446             dot;
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         a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
53464         b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
53465         c = p1y - c1y;
53466         t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
53467         t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
53468         if (Math.abs(t1) > 1e12) {
53469             t1 = 0.5;
53470         }
53471         if (Math.abs(t2) > 1e12) {
53472             t2 = 0.5;
53473         }
53474         if (t1 > 0 && t1 < 1) {
53475             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
53476             x.push(dot.x);
53477             y.push(dot.y);
53478         }
53479         if (t2 > 0 && t2 < 1) {
53480             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
53481             x.push(dot.x);
53482             y.push(dot.y);
53483         }
53484         return {
53485             min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
53486             max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
53487         };
53488     },
53489
53490     /**
53491      * @private
53492      *
53493      * Calculates bezier curve control anchor points for a particular point in a path, with a
53494      * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
53495      * Note that this algorithm assumes that the line being smoothed is normalized going from left
53496      * to right; it makes special adjustments assuming this orientation.
53497      *
53498      * @param {Number} prevX X coordinate of the previous point in the path
53499      * @param {Number} prevY Y coordinate of the previous point in the path
53500      * @param {Number} curX X coordinate of the current point in the path
53501      * @param {Number} curY Y coordinate of the current point in the path
53502      * @param {Number} nextX X coordinate of the next point in the path
53503      * @param {Number} nextY Y coordinate of the next point in the path
53504      * @param {Number} value A value to control the smoothness of the curve; this is used to
53505      * divide the distance between points, so a value of 2 corresponds to
53506      * half the distance between points (a very smooth line) while higher values
53507      * result in less smooth curves. Defaults to 4.
53508      * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
53509      * are the control point for the curve toward the previous path point, and
53510      * x2 and y2 are the control point for the curve toward the next path point.
53511      */
53512     getAnchors: function (prevX, prevY, curX, curY, nextX, nextY, value) {
53513         value = value || 4;
53514         var M = Math,
53515             PI = M.PI,
53516             halfPI = PI / 2,
53517             abs = M.abs,
53518             sin = M.sin,
53519             cos = M.cos,
53520             atan = M.atan,
53521             control1Length, control2Length, control1Angle, control2Angle,
53522             control1X, control1Y, control2X, control2Y, alpha;
53523
53524         // Find the length of each control anchor line, by dividing the horizontal distance
53525         // between points by the value parameter.
53526         control1Length = (curX - prevX) / value;
53527         control2Length = (nextX - curX) / value;
53528
53529         // Determine the angle of each control anchor line. If the middle point is a vertical
53530         // turnaround then we force it to a flat horizontal angle to prevent the curve from
53531         // dipping above or below the middle point. Otherwise we use an angle that points
53532         // toward the previous/next target point.
53533         if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
53534             control1Angle = control2Angle = halfPI;
53535         } else {
53536             control1Angle = atan((curX - prevX) / abs(curY - prevY));
53537             if (prevY < curY) {
53538                 control1Angle = PI - control1Angle;
53539             }
53540             control2Angle = atan((nextX - curX) / abs(curY - nextY));
53541             if (nextY < curY) {
53542                 control2Angle = PI - control2Angle;
53543             }
53544         }
53545
53546         // Adjust the calculated angles so they point away from each other on the same line
53547         alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
53548         if (alpha > halfPI) {
53549             alpha -= PI;
53550         }
53551         control1Angle += alpha;
53552         control2Angle += alpha;
53553
53554         // Find the control anchor points from the angles and length
53555         control1X = curX - control1Length * sin(control1Angle);
53556         control1Y = curY + control1Length * cos(control1Angle);
53557         control2X = curX + control2Length * sin(control2Angle);
53558         control2Y = curY + control2Length * cos(control2Angle);
53559
53560         // One last adjustment, make sure that no control anchor point extends vertically past
53561         // its target prev/next point, as that results in curves dipping above or below and
53562         // bending back strangely. If we find this happening we keep the control angle but
53563         // reduce the length of the control line so it stays within bounds.
53564         if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
53565             control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
53566             control1Y = prevY;
53567         }
53568         if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
53569             control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
53570             control2Y = nextY;
53571         }
53572         
53573         return {
53574             x1: control1X,
53575             y1: control1Y,
53576             x2: control2X,
53577             y2: control2Y
53578         };
53579     },
53580
53581     /* Smoothing function for a path.  Converts a path into cubic beziers.  Value defines the divider of the distance between points.
53582      * Defaults to a value of 4.
53583      */
53584     smooth: function (originalPath, value) {
53585         var path = this.path2curve(originalPath),
53586             newp = [path[0]],
53587             x = path[0][1],
53588             y = path[0][2],
53589             j,
53590             points,
53591             i = 1,
53592             ii = path.length,
53593             beg = 1,
53594             mx = x,
53595             my = y,
53596             cx = 0,
53597             cy = 0;
53598         for (; i < ii; i++) {
53599             var pathi = path[i],
53600                 pathil = pathi.length,
53601                 pathim = path[i - 1],
53602                 pathiml = pathim.length,
53603                 pathip = path[i + 1],
53604                 pathipl = pathip && pathip.length;
53605             if (pathi[0] == "M") {
53606                 mx = pathi[1];
53607                 my = pathi[2];
53608                 j = i + 1;
53609                 while (path[j][0] != "C") {
53610                     j++;
53611                 }
53612                 cx = path[j][5];
53613                 cy = path[j][6];
53614                 newp.push(["M", mx, my]);
53615                 beg = newp.length;
53616                 x = mx;
53617                 y = my;
53618                 continue;
53619             }
53620             if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
53621                 var begl = newp[beg].length;
53622                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
53623                 newp[beg][1] = points.x2;
53624                 newp[beg][2] = points.y2;
53625             }
53626             else if (!pathip || pathip[0] == "M") {
53627                 points = {
53628                     x1: pathi[pathil - 2],
53629                     y1: pathi[pathil - 1]
53630                 };
53631             } else {
53632                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
53633             }
53634             newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
53635             x = points.x2;
53636             y = points.y2;
53637         }
53638         return newp;
53639     },
53640
53641     findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
53642         var t1 = 1 - t;
53643         return {
53644             x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
53645             y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
53646         };
53647     },
53648
53649     /**
53650      * A utility method to deduce an appropriate tick configuration for the data set of given
53651      * feature.
53652      * 
53653      * @param {Number/Date} from The minimum value in the data
53654      * @param {Number/Date} to The maximum value in the data
53655      * @param {Number} stepsMax The maximum number of ticks
53656      * @return {Object} The calculated step and ends info; When `from` and `to` are Dates, refer to the
53657      * return value of {@link #snapEndsByDate}. For numerical `from` and `to` the return value contains:
53658      * @return {Number} return.from The result start value, which may be lower than the original start value
53659      * @return {Number} return.to The result end value, which may be higher than the original end value
53660      * @return {Number} return.power The calculate power.
53661      * @return {Number} return.step The value size of each step
53662      * @return {Number} return.steps The number of steps.
53663      */
53664     snapEnds: function (from, to, stepsMax) {
53665         if (Ext.isDate(from)) {
53666             return this.snapEndsByDate(from, to, stepsMax);
53667         }
53668         var step = (to - from) / stepsMax,
53669             level = Math.floor(Math.log(step) / Math.LN10) + 1,
53670             m = Math.pow(10, level),
53671             cur,
53672             modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
53673             interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
53674             stepCount = 0,
53675             value,
53676             weight,
53677             i,
53678             topValue,
53679             topWeight = 1e9,
53680             ln = interval.length;
53681         cur = from = Math.floor(from / m) * m;
53682         for (i = 0; i < ln; i++) {
53683             value = interval[i][0];
53684             weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
53685             if (weight < topWeight) {
53686                 topValue = value;
53687                 topWeight = weight;
53688             }
53689         }
53690         step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
53691         while (cur < to) {
53692             cur += step;
53693             stepCount++;
53694         }
53695         to = +cur.toFixed(10);
53696         return {
53697             from: from,
53698             to: to,
53699             power: level,
53700             step: step,
53701             steps: stepCount
53702         };
53703     },
53704
53705     /**
53706      * A utility method to deduce an appropriate tick configuration for the data set of given
53707      * feature when data is Dates. Refer to {@link #snapEnds} for numeric data.
53708      *
53709      * @param {Date} from The minimum value in the data
53710      * @param {Date} to The maximum value in the data
53711      * @param {Number} stepsMax The maximum number of ticks
53712      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
53713      * and will not be adjusted
53714      * @return {Object} The calculated step and ends info; properties are:
53715      * @return {Date} return.from The result start value, which may be lower than the original start value
53716      * @return {Date} return.to The result end value, which may be higher than the original end value
53717      * @return {Number} return.step The value size of each step
53718      * @return {Number} return.steps The number of steps.
53719      * NOTE: the steps may not divide the from/to range perfectly evenly;
53720      * there may be a smaller distance between the last step and the end value than between prior
53721      * steps, particularly when the `endsLocked` param is true. Therefore it is best to not use
53722      * the `steps` result when finding the axis tick points, instead use the `step`, `to`, and
53723      * `from` to find the correct point for each tick.
53724      */
53725     snapEndsByDate: function (from, to, stepsMax, lockEnds) {
53726         var selectedStep = false, scales = [
53727                 [Ext.Date.MILLI, [1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500]],
53728                 [Ext.Date.SECOND, [1, 2, 3, 5, 10, 15, 30]],
53729                 [Ext.Date.MINUTE, [1, 2, 3, 5, 10, 20, 30]],
53730                 [Ext.Date.HOUR, [1, 2, 3, 4, 6, 12]],
53731                 [Ext.Date.DAY, [1, 2, 3, 7, 14]],
53732                 [Ext.Date.MONTH, [1, 2, 3, 4, 6]]
53733             ], j, yearDiff;
53734
53735         // Find the most desirable scale
53736         Ext.each(scales, function(scale, i) {
53737             for (j = 0; j < scale[1].length; j++) {
53738                 if (to < Ext.Date.add(from, scale[0], scale[1][j] * stepsMax)) {
53739                     selectedStep = [scale[0], scale[1][j]];
53740                     return false;
53741                 }
53742             }
53743         });
53744         if (!selectedStep) {
53745             yearDiff = this.snapEnds(from.getFullYear(), to.getFullYear() + 1, stepsMax, lockEnds);
53746             selectedStep = [Date.YEAR, Math.round(yearDiff.step)];
53747         }
53748         return this.snapEndsByDateAndStep(from, to, selectedStep, lockEnds);
53749     },
53750
53751
53752     /**
53753      * A utility method to deduce an appropriate tick configuration for the data set of given
53754      * feature and specific step size.
53755      * @param {Date} from The minimum value in the data
53756      * @param {Date} to The maximum value in the data
53757      * @param {Array} step An array with two components: The first is the unit of the step (day, month, year, etc).
53758      * The second one is the number of units for the step (1, 2, etc.).
53759      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
53760      * and will not be adjusted
53761      * @return {Object} See the return value of {@link #snapEndsByDate}.
53762      */
53763     snapEndsByDateAndStep: function(from, to, step, lockEnds) {
53764         var fromStat = [from.getFullYear(), from.getMonth(), from.getDate(),
53765                 from.getHours(), from.getMinutes(), from.getSeconds(), from.getMilliseconds()],
53766             steps = 0, testFrom, testTo;
53767         if (lockEnds) {
53768             testFrom = from;
53769         } else {
53770             switch (step[0]) {
53771                 case Ext.Date.MILLI:
53772                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53773                             fromStat[4], fromStat[5], Math.floor(fromStat[6] / step[1]) * step[1]);
53774                     break;
53775                 case Ext.Date.SECOND:
53776                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53777                             fromStat[4], Math.floor(fromStat[5] / step[1]) * step[1], 0);
53778                     break;
53779                 case Ext.Date.MINUTE:
53780                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53781                             Math.floor(fromStat[4] / step[1]) * step[1], 0, 0);
53782                     break;
53783                 case Ext.Date.HOUR:
53784                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2],
53785                             Math.floor(fromStat[3] / step[1]) * step[1], 0, 0, 0);
53786                     break;
53787                 case Ext.Date.DAY:
53788                     testFrom = new Date(fromStat[0], fromStat[1],
53789                             Math.floor(fromStat[2] - 1 / step[1]) * step[1] + 1, 0, 0, 0, 0);
53790                     break;
53791                 case Ext.Date.MONTH:
53792                     testFrom = new Date(fromStat[0], Math.floor(fromStat[1] / step[1]) * step[1], 1, 0, 0, 0, 0);
53793                     break;
53794                 default: // Ext.Date.YEAR
53795                     testFrom = new Date(Math.floor(fromStat[0] / step[1]) * step[1], 0, 1, 0, 0, 0, 0);
53796                     break;
53797             }
53798         }
53799
53800         testTo = testFrom;
53801         // TODO(zhangbei) : We can do it better somehow...
53802         while (testTo < to) {
53803             testTo = Ext.Date.add(testTo, step[0], step[1]);
53804             steps++;
53805         }
53806
53807         if (lockEnds) {
53808             testTo = to;
53809         }
53810         return {
53811             from : +testFrom,
53812             to : +testTo,
53813             step : (testTo - testFrom) / steps,
53814             steps : steps
53815         };
53816     },
53817
53818     sorter: function (a, b) {
53819         return a.offset - b.offset;
53820     },
53821
53822     rad: function(degrees) {
53823         return degrees % 360 * Math.PI / 180;
53824     },
53825
53826     degrees: function(radian) {
53827         return radian * 180 / Math.PI % 360;
53828     },
53829
53830     withinBox: function(x, y, bbox) {
53831         bbox = bbox || {};
53832         return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
53833     },
53834
53835     parseGradient: function(gradient) {
53836         var me = this,
53837             type = gradient.type || 'linear',
53838             angle = gradient.angle || 0,
53839             radian = me.radian,
53840             stops = gradient.stops,
53841             stopsArr = [],
53842             stop,
53843             vector,
53844             max,
53845             stopObj;
53846
53847         if (type == 'linear') {
53848             vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
53849             max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
53850             vector[2] *= max;
53851             vector[3] *= max;
53852             if (vector[2] < 0) {
53853                 vector[0] = -vector[2];
53854                 vector[2] = 0;
53855             }
53856             if (vector[3] < 0) {
53857                 vector[1] = -vector[3];
53858                 vector[3] = 0;
53859             }
53860         }
53861
53862         for (stop in stops) {
53863             if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
53864                 stopObj = {
53865                     offset: parseInt(stop, 10),
53866                     color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
53867                     opacity: stops[stop].opacity || 1
53868                 };
53869                 stopsArr.push(stopObj);
53870             }
53871         }
53872         // Sort by pct property
53873         Ext.Array.sort(stopsArr, me.sorter);
53874         if (type == 'linear') {
53875             return {
53876                 id: gradient.id,
53877                 type: type,
53878                 vector: vector,
53879                 stops: stopsArr
53880             };
53881         }
53882         else {
53883             return {
53884                 id: gradient.id,
53885                 type: type,
53886                 centerX: gradient.centerX,
53887                 centerY: gradient.centerY,
53888                 focalX: gradient.focalX,
53889                 focalY: gradient.focalY,
53890                 radius: gradient.radius,
53891                 vector: vector,
53892                 stops: stopsArr
53893             };
53894         }
53895     }
53896 });
53897
53898
53899 /**
53900  * @class Ext.fx.PropertyHandler
53901  * @ignore
53902  */
53903 Ext.define('Ext.fx.PropertyHandler', {
53904
53905     /* Begin Definitions */
53906
53907     requires: ['Ext.draw.Draw'],
53908
53909     statics: {
53910         defaultHandler: {
53911             pixelDefaultsRE: /width|height|top$|bottom$|left$|right$/i,
53912             unitRE: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/,
53913             scrollRE: /^scroll/i,
53914
53915             computeDelta: function(from, end, damper, initial, attr) {
53916                 damper = (typeof damper == 'number') ? damper : 1;
53917                 var unitRE = this.unitRE,
53918                     match = unitRE.exec(from),
53919                     start, units;
53920                 if (match) {
53921                     from = match[1];
53922                     units = match[2];
53923                     if (!this.scrollRE.test(attr) && !units && this.pixelDefaultsRE.test(attr)) {
53924                         units = 'px';
53925                     }
53926                 }
53927                 from = +from || 0;
53928
53929                 match = unitRE.exec(end);
53930                 if (match) {
53931                     end = match[1];
53932                     units = match[2] || units;
53933                 }
53934                 end = +end || 0;
53935                 start = (initial != null) ? initial : from;
53936                 return {
53937                     from: from,
53938                     delta: (end - start) * damper,
53939                     units: units
53940                 };
53941             },
53942
53943             get: function(from, end, damper, initialFrom, attr) {
53944                 var ln = from.length,
53945                     out = [],
53946                     i, initial, res, j, len;
53947                 for (i = 0; i < ln; i++) {
53948                     if (initialFrom) {
53949                         initial = initialFrom[i][1].from;
53950                     }
53951                     if (Ext.isArray(from[i][1]) && Ext.isArray(end)) {
53952                         res = [];
53953                         j = 0;
53954                         len = from[i][1].length;
53955                         for (; j < len; j++) {
53956                             res.push(this.computeDelta(from[i][1][j], end[j], damper, initial, attr));
53957                         }
53958                         out.push([from[i][0], res]);
53959                     }
53960                     else {
53961                         out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
53962                     }
53963                 }
53964                 return out;
53965             },
53966
53967             set: function(values, easing) {
53968                 var ln = values.length,
53969                     out = [],
53970                     i, val, res, len, j;
53971                 for (i = 0; i < ln; i++) {
53972                     val  = values[i][1];
53973                     if (Ext.isArray(val)) {
53974                         res = [];
53975                         j = 0;
53976                         len = val.length;
53977                         for (; j < len; j++) {
53978                             res.push(val[j].from + (val[j].delta * easing) + (val[j].units || 0));
53979                         }
53980                         out.push([values[i][0], res]);
53981                     } else {
53982                         out.push([values[i][0], val.from + (val.delta * easing) + (val.units || 0)]);
53983                     }
53984                 }
53985                 return out;
53986             }
53987         },
53988         color: {
53989             rgbRE: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
53990             hexRE: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
53991             hex3RE: /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
53992
53993             parseColor : function(color, damper) {
53994                 damper = (typeof damper == 'number') ? damper : 1;
53995                 var base,
53996                     out = false,
53997                     match;
53998
53999                 Ext.each([this.hexRE, this.rgbRE, this.hex3RE], function(re, idx) {
54000                     base = (idx % 2 == 0) ? 16 : 10;
54001                     match = re.exec(color);
54002                     if (match && match.length == 4) {
54003                         if (idx == 2) {
54004                             match[1] += match[1];
54005                             match[2] += match[2];
54006                             match[3] += match[3];
54007                         }
54008                         out = {
54009                             red: parseInt(match[1], base),
54010                             green: parseInt(match[2], base),
54011                             blue: parseInt(match[3], base)
54012                         };
54013                         return false;
54014                     }
54015                 });
54016                 return out || color;
54017             },
54018
54019             computeDelta: function(from, end, damper, initial) {
54020                 from = this.parseColor(from);
54021                 end = this.parseColor(end, damper);
54022                 var start = initial ? initial : from,
54023                     tfrom = typeof start,
54024                     tend = typeof end;
54025                 //Extra check for when the color string is not recognized.
54026                 if (tfrom == 'string' ||  tfrom == 'undefined' 
54027                   || tend == 'string' || tend == 'undefined') {
54028                     return end || start;
54029                 }
54030                 return {
54031                     from:  from,
54032                     delta: {
54033                         red: Math.round((end.red - start.red) * damper),
54034                         green: Math.round((end.green - start.green) * damper),
54035                         blue: Math.round((end.blue - start.blue) * damper)
54036                     }
54037                 };
54038             },
54039
54040             get: function(start, end, damper, initialFrom) {
54041                 var ln = start.length,
54042                     out = [],
54043                     i, initial;
54044                 for (i = 0; i < ln; i++) {
54045                     if (initialFrom) {
54046                         initial = initialFrom[i][1].from;
54047                     }
54048                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
54049                 }
54050                 return out;
54051             },
54052
54053             set: function(values, easing) {
54054                 var ln = values.length,
54055                     out = [],
54056                     i, val, parsedString, from, delta;
54057                 for (i = 0; i < ln; i++) {
54058                     val = values[i][1];
54059                     if (val) {
54060                         from = val.from;
54061                         delta = val.delta;
54062                         //multiple checks to reformat the color if it can't recognized by computeDelta.
54063                         val = (typeof val == 'object' && 'red' in val)? 
54064                                 'rgb(' + val.red + ', ' + val.green + ', ' + val.blue + ')' : val;
54065                         val = (typeof val == 'object' && val.length)? val[0] : val;
54066                         if (typeof val == 'undefined') {
54067                             return [];
54068                         }
54069                         parsedString = typeof val == 'string'? val :
54070                             'rgb(' + [
54071                                   (from.red + Math.round(delta.red * easing)) % 256,
54072                                   (from.green + Math.round(delta.green * easing)) % 256,
54073                                   (from.blue + Math.round(delta.blue * easing)) % 256
54074                               ].join(',') + ')';
54075                         out.push([
54076                             values[i][0],
54077                             parsedString
54078                         ]);
54079                     }
54080                 }
54081                 return out;
54082             }
54083         },
54084         object: {
54085             interpolate: function(prop, damper) {
54086                 damper = (typeof damper == 'number') ? damper : 1;
54087                 var out = {},
54088                     p;
54089                 for(p in prop) {
54090                     out[p] = parseInt(prop[p], 10) * damper;
54091                 }
54092                 return out;
54093             },
54094
54095             computeDelta: function(from, end, damper, initial) {
54096                 from = this.interpolate(from);
54097                 end = this.interpolate(end, damper);
54098                 var start = initial ? initial : from,
54099                     delta = {},
54100                     p;
54101
54102                 for(p in end) {
54103                     delta[p] = end[p] - start[p];
54104                 }
54105                 return {
54106                     from:  from,
54107                     delta: delta
54108                 };
54109             },
54110
54111             get: function(start, end, damper, initialFrom) {
54112                 var ln = start.length,
54113                     out = [],
54114                     i, initial;
54115                 for (i = 0; i < ln; i++) {
54116                     if (initialFrom) {
54117                         initial = initialFrom[i][1].from;
54118                     }
54119                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
54120                 }
54121                 return out;
54122             },
54123
54124             set: function(values, easing) {
54125                 var ln = values.length,
54126                     out = [],
54127                     outObject = {},
54128                     i, from, delta, val, p;
54129                 for (i = 0; i < ln; i++) {
54130                     val  = values[i][1];
54131                     from = val.from;
54132                     delta = val.delta;
54133                     for (p in from) {
54134                         outObject[p] = Math.round(from[p] + delta[p] * easing);
54135                     }
54136                     out.push([
54137                         values[i][0],
54138                         outObject
54139                     ]);
54140                 }
54141                 return out;
54142             }
54143         },
54144
54145         path: {
54146             computeDelta: function(from, end, damper, initial) {
54147                 damper = (typeof damper == 'number') ? damper : 1;
54148                 var start;
54149                 from = +from || 0;
54150                 end = +end || 0;
54151                 start = (initial != null) ? initial : from;
54152                 return {
54153                     from: from,
54154                     delta: (end - start) * damper
54155                 };
54156             },
54157
54158             forcePath: function(path) {
54159                 if (!Ext.isArray(path) && !Ext.isArray(path[0])) {
54160                     path = Ext.draw.Draw.parsePathString(path);
54161                 }
54162                 return path;
54163             },
54164
54165             get: function(start, end, damper, initialFrom) {
54166                 var endPath = this.forcePath(end),
54167                     out = [],
54168                     startLn = start.length,
54169                     startPathLn, pointsLn, i, deltaPath, initial, j, k, path, startPath;
54170                 for (i = 0; i < startLn; i++) {
54171                     startPath = this.forcePath(start[i][1]);
54172
54173                     deltaPath = Ext.draw.Draw.interpolatePaths(startPath, endPath);
54174                     startPath = deltaPath[0];
54175                     endPath = deltaPath[1];
54176
54177                     startPathLn = startPath.length;
54178                     path = [];
54179                     for (j = 0; j < startPathLn; j++) {
54180                         deltaPath = [startPath[j][0]];
54181                         pointsLn = startPath[j].length;
54182                         for (k = 1; k < pointsLn; k++) {
54183                             initial = initialFrom && initialFrom[0][1][j][k].from;
54184                             deltaPath.push(this.computeDelta(startPath[j][k], endPath[j][k], damper, initial));
54185                         }
54186                         path.push(deltaPath);
54187                     }
54188                     out.push([start[i][0], path]);
54189                 }
54190                 return out;
54191             },
54192
54193             set: function(values, easing) {
54194                 var ln = values.length,
54195                     out = [],
54196                     i, j, k, newPath, calcPath, deltaPath, deltaPathLn, pointsLn;
54197                 for (i = 0; i < ln; i++) {
54198                     deltaPath = values[i][1];
54199                     newPath = [];
54200                     deltaPathLn = deltaPath.length;
54201                     for (j = 0; j < deltaPathLn; j++) {
54202                         calcPath = [deltaPath[j][0]];
54203                         pointsLn = deltaPath[j].length;
54204                         for (k = 1; k < pointsLn; k++) {
54205                             calcPath.push(deltaPath[j][k].from + deltaPath[j][k].delta * easing);
54206                         }
54207                         newPath.push(calcPath.join(','));
54208                     }
54209                     out.push([values[i][0], newPath.join(',')]);
54210                 }
54211                 return out;
54212             }
54213         }
54214         /* End Definitions */
54215     }
54216 }, function() {
54217     Ext.each([
54218         'outlineColor',
54219         'backgroundColor',
54220         'borderColor',
54221         'borderTopColor',
54222         'borderRightColor', 
54223         'borderBottomColor', 
54224         'borderLeftColor',
54225         'fill',
54226         'stroke'
54227     ], function(prop) {
54228         this[prop] = this.color;
54229     }, this);
54230 });
54231 /**
54232  * @class Ext.fx.Anim
54233  *
54234  * This class manages animation for a specific {@link #target}. The animation allows
54235  * animation of various properties on the target, such as size, position, color and others.
54236  *
54237  * ## Starting Conditions
54238  * The starting conditions for the animation are provided by the {@link #from} configuration.
54239  * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
54240  * property is not defined, the starting value for that property will be read directly from the target.
54241  *
54242  * ## End Conditions
54243  * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
54244  * the final values once the animations has finished. The values in the {@link #from} can mirror
54245  * those in the {@link #to} configuration to provide a starting point.
54246  *
54247  * ## Other Options
54248  *  - {@link #duration}: Specifies the time period of the animation.
54249  *  - {@link #easing}: Specifies the easing of the animation.
54250  *  - {@link #iterations}: Allows the animation to repeat a number of times.
54251  *  - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
54252  *
54253  * ## Example Code
54254  *
54255  *     @example
54256  *     var myComponent = Ext.create('Ext.Component', {
54257  *         renderTo: document.body,
54258  *         width: 200,
54259  *         height: 200,
54260  *         style: 'border: 1px solid red;'
54261  *     });
54262  *
54263  *     Ext.create('Ext.fx.Anim', {
54264  *         target: myComponent,
54265  *         duration: 1000,
54266  *         from: {
54267  *             width: 400 //starting width 400
54268  *         },
54269  *         to: {
54270  *             width: 300, //end width 300
54271  *             height: 300 // end width 300
54272  *         }
54273  *     });
54274  */
54275 Ext.define('Ext.fx.Anim', {
54276
54277     /* Begin Definitions */
54278
54279     mixins: {
54280         observable: 'Ext.util.Observable'
54281     },
54282
54283     requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
54284
54285     /* End Definitions */
54286
54287     isAnimation: true,
54288
54289     /**
54290      * @cfg {Function} callback
54291      * A function to be run after the animation has completed.
54292      */
54293
54294     /**
54295      * @cfg {Function} scope
54296      * The scope that the {@link #callback} function will be called with
54297      */
54298
54299     /**
54300      * @cfg {Number} duration
54301      * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
54302      * specified, then each animate will take the same duration for each iteration.
54303      */
54304     duration: 250,
54305
54306     /**
54307      * @cfg {Number} delay
54308      * Time to delay before starting the animation. Defaults to 0.
54309      */
54310     delay: 0,
54311
54312     /* private used to track a delayed starting time */
54313     delayStart: 0,
54314
54315     /**
54316      * @cfg {Boolean} dynamic
54317      * 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.
54318      */
54319     dynamic: false,
54320
54321     /**
54322      * @cfg {String} easing
54323 This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
54324 speed over its duration.
54325
54326          -backIn
54327          -backOut
54328          -bounceIn
54329          -bounceOut
54330          -ease
54331          -easeIn
54332          -easeOut
54333          -easeInOut
54334          -elasticIn
54335          -elasticOut
54336          -cubic-bezier(x1, y1, x2, y2)
54337
54338 Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
54339 specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
54340 be in the range [0, 1] or the definition is invalid.
54341
54342 [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
54343
54344      * @markdown
54345      */
54346     easing: 'ease',
54347
54348      /**
54349       * @cfg {Object} keyframes
54350       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
54351       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
54352       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
54353       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
54354       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
54355  <pre><code>
54356 keyframes : {
54357     '0%': {
54358         left: 100
54359     },
54360     '40%': {
54361         left: 150
54362     },
54363     '60%': {
54364         left: 75
54365     },
54366     '100%': {
54367         left: 100
54368     }
54369 }
54370  </code></pre>
54371       */
54372
54373     /**
54374      * @private
54375      */
54376     damper: 1,
54377
54378     /**
54379      * @private
54380      */
54381     bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
54382
54383     /**
54384      * Run the animation from the end to the beginning
54385      * Defaults to false.
54386      * @cfg {Boolean} reverse
54387      */
54388     reverse: false,
54389
54390     /**
54391      * Flag to determine if the animation has started
54392      * @property running
54393      * @type Boolean
54394      */
54395     running: false,
54396
54397     /**
54398      * Flag to determine if the animation is paused. Only set this to true if you need to
54399      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
54400      * @property paused
54401      * @type Boolean
54402      */
54403     paused: false,
54404
54405     /**
54406      * Number of times to execute the animation. Defaults to 1.
54407      * @cfg {Number} iterations
54408      */
54409     iterations: 1,
54410
54411     /**
54412      * Used in conjunction with iterations to reverse the animation each time an iteration completes.
54413      * @cfg {Boolean} alternate
54414      * Defaults to false.
54415      */
54416     alternate: false,
54417
54418     /**
54419      * Current iteration the animation is running.
54420      * @property currentIteration
54421      * @type Number
54422      */
54423     currentIteration: 0,
54424
54425     /**
54426      * Starting time of the animation.
54427      * @property startTime
54428      * @type Date
54429      */
54430     startTime: 0,
54431
54432     /**
54433      * Contains a cache of the interpolators to be used.
54434      * @private
54435      * @property propHandlers
54436      * @type Object
54437      */
54438
54439     /**
54440      * @cfg {String/Object} target
54441      * The {@link Ext.fx.target.Target} to apply the animation to.  This should only be specified when creating an Ext.fx.Anim directly.
54442      * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
54443      * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
54444      * automatically.
54445      */
54446
54447     /**
54448      * @cfg {Object} from
54449      * An object containing property/value pairs for the beginning of the animation.  If not specified, the current state of the
54450      * Ext.fx.target will be used. For example:
54451 <pre><code>
54452 from : {
54453     opacity: 0,       // Transparent
54454     color: '#ffffff', // White
54455     left: 0
54456 }
54457 </code></pre>
54458      */
54459
54460     /**
54461      * @cfg {Object} to
54462      * An object containing property/value pairs for the end of the animation. For example:
54463  <pre><code>
54464  to : {
54465      opacity: 1,       // Opaque
54466      color: '#00ff00', // Green
54467      left: 500
54468  }
54469  </code></pre>
54470      */
54471
54472     // @private
54473     constructor: function(config) {
54474         var me = this,
54475             curve;
54476             
54477         config = config || {};
54478         // If keyframes are passed, they really want an Animator instead.
54479         if (config.keyframes) {
54480             return Ext.create('Ext.fx.Animator', config);
54481         }
54482         config = Ext.apply(me, config);
54483         if (me.from === undefined) {
54484             me.from = {};
54485         }
54486         me.propHandlers = {};
54487         me.config = config;
54488         me.target = Ext.fx.Manager.createTarget(me.target);
54489         me.easingFn = Ext.fx.Easing[me.easing];
54490         me.target.dynamic = me.dynamic;
54491
54492         // If not a pre-defined curve, try a cubic-bezier
54493         if (!me.easingFn) {
54494             me.easingFn = String(me.easing).match(me.bezierRE);
54495             if (me.easingFn && me.easingFn.length == 5) {
54496                 curve = me.easingFn;
54497                 me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
54498             }
54499         }
54500         me.id = Ext.id(null, 'ext-anim-');
54501         Ext.fx.Manager.addAnim(me);
54502         me.addEvents(
54503             /**
54504              * @event beforeanimate
54505              * Fires before the animation starts. A handler can return false to cancel the animation.
54506              * @param {Ext.fx.Anim} this
54507              */
54508             'beforeanimate',
54509              /**
54510               * @event afteranimate
54511               * Fires when the animation is complete.
54512               * @param {Ext.fx.Anim} this
54513               * @param {Date} startTime
54514               */
54515             'afteranimate',
54516              /**
54517               * @event lastframe
54518               * Fires when the animation's last frame has been set.
54519               * @param {Ext.fx.Anim} this
54520               * @param {Date} startTime
54521               */
54522             'lastframe'
54523         );
54524         me.mixins.observable.constructor.call(me, config);
54525         if (config.callback) {
54526             me.on('afteranimate', config.callback, config.scope);
54527         }
54528         return me;
54529     },
54530
54531     /**
54532      * @private
54533      * Helper to the target
54534      */
54535     setAttr: function(attr, value) {
54536         return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
54537     },
54538
54539     /**
54540      * @private
54541      * Set up the initial currentAttrs hash.
54542      */
54543     initAttrs: function() {
54544         var me = this,
54545             from = me.from,
54546             to = me.to,
54547             initialFrom = me.initialFrom || {},
54548             out = {},
54549             start, end, propHandler, attr;
54550
54551         for (attr in to) {
54552             if (to.hasOwnProperty(attr)) {
54553                 start = me.target.getAttr(attr, from[attr]);
54554                 end = to[attr];
54555                 // Use default (numeric) property handler
54556                 if (!Ext.fx.PropertyHandler[attr]) {
54557                     if (Ext.isObject(end)) {
54558                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
54559                     } else {
54560                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
54561                     }
54562                 }
54563                 // Use custom handler
54564                 else {
54565                     propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
54566                 }
54567                 out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
54568             }
54569         }
54570         me.currentAttrs = out;
54571     },
54572
54573     /**
54574      * @private
54575      * Fires beforeanimate and sets the running flag.
54576      */
54577     start: function(startTime) {
54578         var me = this,
54579             delay = me.delay,
54580             delayStart = me.delayStart,
54581             delayDelta;
54582         if (delay) {
54583             if (!delayStart) {
54584                 me.delayStart = startTime;
54585                 return;
54586             }
54587             else {
54588                 delayDelta = startTime - delayStart;
54589                 if (delayDelta < delay) {
54590                     return;
54591                 }
54592                 else {
54593                     // Compensate for frame delay;
54594                     startTime = new Date(delayStart.getTime() + delay);
54595                 }
54596             }
54597         }
54598         if (me.fireEvent('beforeanimate', me) !== false) {
54599             me.startTime = startTime;
54600             if (!me.paused && !me.currentAttrs) {
54601                 me.initAttrs();
54602             }
54603             me.running = true;
54604         }
54605     },
54606
54607     /**
54608      * @private
54609      * Calculate attribute value at the passed timestamp.
54610      * @returns a hash of the new attributes.
54611      */
54612     runAnim: function(elapsedTime) {
54613         var me = this,
54614             attrs = me.currentAttrs,
54615             duration = me.duration,
54616             easingFn = me.easingFn,
54617             propHandlers = me.propHandlers,
54618             ret = {},
54619             easing, values, attr, lastFrame;
54620
54621         if (elapsedTime >= duration) {
54622             elapsedTime = duration;
54623             lastFrame = true;
54624         }
54625         if (me.reverse) {
54626             elapsedTime = duration - elapsedTime;
54627         }
54628
54629         for (attr in attrs) {
54630             if (attrs.hasOwnProperty(attr)) {
54631                 values = attrs[attr];
54632                 easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
54633                 ret[attr] = propHandlers[attr].set(values, easing);
54634             }
54635         }
54636         return ret;
54637     },
54638
54639     /**
54640      * @private
54641      * Perform lastFrame cleanup and handle iterations
54642      * @returns a hash of the new attributes.
54643      */
54644     lastFrame: function() {
54645         var me = this,
54646             iter = me.iterations,
54647             iterCount = me.currentIteration;
54648
54649         iterCount++;
54650         if (iterCount < iter) {
54651             if (me.alternate) {
54652                 me.reverse = !me.reverse;
54653             }
54654             me.startTime = new Date();
54655             me.currentIteration = iterCount;
54656             // Turn off paused for CSS3 Transitions
54657             me.paused = false;
54658         }
54659         else {
54660             me.currentIteration = 0;
54661             me.end();
54662             me.fireEvent('lastframe', me, me.startTime);
54663         }
54664     },
54665
54666     /**
54667      * Fire afteranimate event and end the animation. Usually called automatically when the
54668      * animation reaches its final frame, but can also be called manually to pre-emptively
54669      * stop and destroy the running animation.
54670      */
54671     end: function() {
54672         var me = this;
54673         me.startTime = 0;
54674         me.paused = false;
54675         me.running = false;
54676         Ext.fx.Manager.removeAnim(me);
54677         me.fireEvent('afteranimate', me, me.startTime);
54678     }
54679 });
54680 // Set flag to indicate that Fx is available. Class might not be available immediately.
54681 Ext.enableFx = true;
54682
54683 /*
54684  * This is a derivative of the similarly named class in the YUI Library.
54685  * The original license:
54686  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
54687  * Code licensed under the BSD License:
54688  * http://developer.yahoo.net/yui/license.txt
54689  */
54690
54691
54692 /**
54693  * Defines the interface and base operation of items that that can be
54694  * dragged or can be drop targets.  It was designed to be extended, overriding
54695  * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
54696  * Up to three html elements can be associated with a DragDrop instance:
54697  *
54698  * - linked element: the element that is passed into the constructor.
54699  *   This is the element which defines the boundaries for interaction with
54700  *   other DragDrop objects.
54701  *
54702  * - handle element(s): The drag operation only occurs if the element that
54703  *   was clicked matches a handle element.  By default this is the linked
54704  *   element, but there are times that you will want only a portion of the
54705  *   linked element to initiate the drag operation, and the setHandleElId()
54706  *   method provides a way to define this.
54707  *
54708  * - drag element: this represents the element that would be moved along
54709  *   with the cursor during a drag operation.  By default, this is the linked
54710  *   element itself as in {@link Ext.dd.DD}.  setDragElId() lets you define
54711  *   a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
54712  *
54713  * This class should not be instantiated until the onload event to ensure that
54714  * the associated elements are available.
54715  * The following would define a DragDrop obj that would interact with any
54716  * other DragDrop obj in the "group1" group:
54717  *
54718  *     dd = new Ext.dd.DragDrop("div1", "group1");
54719  *
54720  * Since none of the event handlers have been implemented, nothing would
54721  * actually happen if you were to run the code above.  Normally you would
54722  * override this class or one of the default implementations, but you can
54723  * also override the methods you want on an instance of the class...
54724  *
54725  *     dd.onDragDrop = function(e, id) {
54726  *         alert("dd was dropped on " + id);
54727  *     }
54728  *
54729  */
54730 Ext.define('Ext.dd.DragDrop', {
54731     requires: ['Ext.dd.DragDropManager'],
54732
54733     /**
54734      * Creates new DragDrop.
54735      * @param {String} id of the element that is linked to this instance
54736      * @param {String} sGroup the group of related DragDrop objects
54737      * @param {Object} config an object containing configurable attributes.
54738      * Valid properties for DragDrop:
54739      *
54740      * - padding
54741      * - isTarget
54742      * - maintainOffset
54743      * - primaryButtonOnly
54744      */
54745     constructor: function(id, sGroup, config) {
54746         if(id) {
54747             this.init(id, sGroup, config);
54748         }
54749     },
54750
54751     /**
54752      * Set to false to enable a DragDrop object to fire drag events while dragging
54753      * over its own Element. Defaults to true - DragDrop objects do not by default
54754      * fire drag events to themselves.
54755      * @property ignoreSelf
54756      * @type Boolean
54757      */
54758
54759     /**
54760      * The id of the element associated with this object.  This is what we
54761      * refer to as the "linked element" because the size and position of
54762      * this element is used to determine when the drag and drop objects have
54763      * interacted.
54764      * @property id
54765      * @type String
54766      */
54767     id: null,
54768
54769     /**
54770      * Configuration attributes passed into the constructor
54771      * @property config
54772      * @type Object
54773      */
54774     config: null,
54775
54776     /**
54777      * The id of the element that will be dragged.  By default this is same
54778      * as the linked element, but could be changed to another element. Ex:
54779      * Ext.dd.DDProxy
54780      * @property dragElId
54781      * @type String
54782      * @private
54783      */
54784     dragElId: null,
54785
54786     /**
54787      * The ID of the element that initiates the drag operation.  By default
54788      * this is the linked element, but could be changed to be a child of this
54789      * element.  This lets us do things like only starting the drag when the
54790      * header element within the linked html element is clicked.
54791      * @property handleElId
54792      * @type String
54793      * @private
54794      */
54795     handleElId: null,
54796
54797     /**
54798      * An object who's property names identify HTML tags to be considered invalid as drag handles.
54799      * A non-null property value identifies the tag as invalid. Defaults to the
54800      * following value which prevents drag operations from being initiated by &lt;a> elements:<pre><code>
54801 {
54802     A: "A"
54803 }</code></pre>
54804      * @property invalidHandleTypes
54805      * @type Object
54806      */
54807     invalidHandleTypes: null,
54808
54809     /**
54810      * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
54811      * A non-null property value identifies the ID as invalid. For example, to prevent
54812      * dragging from being initiated on element ID "foo", use:<pre><code>
54813 {
54814     foo: true
54815 }</code></pre>
54816      * @property invalidHandleIds
54817      * @type Object
54818      */
54819     invalidHandleIds: null,
54820
54821     /**
54822      * An Array of CSS class names for elements to be considered in valid as drag handles.
54823      * @property {String[]} invalidHandleClasses
54824      */
54825     invalidHandleClasses: null,
54826
54827     /**
54828      * The linked element's absolute X position at the time the drag was
54829      * started
54830      * @property startPageX
54831      * @type Number
54832      * @private
54833      */
54834     startPageX: 0,
54835
54836     /**
54837      * The linked element's absolute X position at the time the drag was
54838      * started
54839      * @property startPageY
54840      * @type Number
54841      * @private
54842      */
54843     startPageY: 0,
54844
54845     /**
54846      * The group defines a logical collection of DragDrop objects that are
54847      * related.  Instances only get events when interacting with other
54848      * DragDrop object in the same group.  This lets us define multiple
54849      * groups using a single DragDrop subclass if we want.
54850      * @property groups
54851      * @type Object An object in the format {'group1':true, 'group2':true}
54852      */
54853     groups: null,
54854
54855     /**
54856      * Individual drag/drop instances can be locked.  This will prevent
54857      * onmousedown start drag.
54858      * @property locked
54859      * @type Boolean
54860      * @private
54861      */
54862     locked: false,
54863
54864     /**
54865      * Locks this instance
54866      */
54867     lock: function() {
54868         this.locked = true;
54869     },
54870
54871     /**
54872      * When set to true, other DD objects in cooperating DDGroups do not receive
54873      * notification events when this DD object is dragged over them. Defaults to false.
54874      * @property moveOnly
54875      * @type Boolean
54876      */
54877     moveOnly: false,
54878
54879     /**
54880      * Unlocks this instace
54881      */
54882     unlock: function() {
54883         this.locked = false;
54884     },
54885
54886     /**
54887      * By default, all instances can be a drop target.  This can be disabled by
54888      * setting isTarget to false.
54889      * @property isTarget
54890      * @type Boolean
54891      */
54892     isTarget: true,
54893
54894     /**
54895      * The padding configured for this drag and drop object for calculating
54896      * the drop zone intersection with this object.
54897      * An array containing the 4 padding values: [top, right, bottom, left]
54898      * @property {Number[]} padding
54899      */
54900     padding: null,
54901
54902     /**
54903      * Cached reference to the linked element
54904      * @property _domRef
54905      * @private
54906      */
54907     _domRef: null,
54908
54909     /**
54910      * Internal typeof flag
54911      * @property __ygDragDrop
54912      * @private
54913      */
54914     __ygDragDrop: true,
54915
54916     /**
54917      * Set to true when horizontal contraints are applied
54918      * @property constrainX
54919      * @type Boolean
54920      * @private
54921      */
54922     constrainX: false,
54923
54924     /**
54925      * Set to true when vertical contraints are applied
54926      * @property constrainY
54927      * @type Boolean
54928      * @private
54929      */
54930     constrainY: false,
54931
54932     /**
54933      * The left constraint
54934      * @property minX
54935      * @type Number
54936      * @private
54937      */
54938     minX: 0,
54939
54940     /**
54941      * The right constraint
54942      * @property maxX
54943      * @type Number
54944      * @private
54945      */
54946     maxX: 0,
54947
54948     /**
54949      * The up constraint
54950      * @property minY
54951      * @type Number
54952      * @private
54953      */
54954     minY: 0,
54955
54956     /**
54957      * The down constraint
54958      * @property maxY
54959      * @type Number
54960      * @private
54961      */
54962     maxY: 0,
54963
54964     /**
54965      * Maintain offsets when we resetconstraints.  Set to true when you want
54966      * the position of the element relative to its parent to stay the same
54967      * when the page changes
54968      *
54969      * @property maintainOffset
54970      * @type Boolean
54971      */
54972     maintainOffset: false,
54973
54974     /**
54975      * Array of pixel locations the element will snap to if we specified a
54976      * horizontal graduation/interval.  This array is generated automatically
54977      * when you define a tick interval.
54978      * @property {Number[]} xTicks
54979      */
54980     xTicks: null,
54981
54982     /**
54983      * Array of pixel locations the element will snap to if we specified a
54984      * vertical graduation/interval.  This array is generated automatically
54985      * when you define a tick interval.
54986      * @property {Number[]} yTicks
54987      */
54988     yTicks: null,
54989
54990     /**
54991      * By default the drag and drop instance will only respond to the primary
54992      * button click (left button for a right-handed mouse).  Set to true to
54993      * allow drag and drop to start with any mouse click that is propogated
54994      * by the browser
54995      * @property primaryButtonOnly
54996      * @type Boolean
54997      */
54998     primaryButtonOnly: true,
54999
55000     /**
55001      * The available property is false until the linked dom element is accessible.
55002      * @property available
55003      * @type Boolean
55004      */
55005     available: false,
55006
55007     /**
55008      * By default, drags can only be initiated if the mousedown occurs in the
55009      * region the linked element is.  This is done in part to work around a
55010      * bug in some browsers that mis-report the mousedown if the previous
55011      * mouseup happened outside of the window.  This property is set to true
55012      * if outer handles are defined. Defaults to false.
55013      *
55014      * @property hasOuterHandles
55015      * @type Boolean
55016      */
55017     hasOuterHandles: false,
55018
55019     /**
55020      * Code that executes immediately before the startDrag event
55021      * @private
55022      */
55023     b4StartDrag: function(x, y) { },
55024
55025     /**
55026      * Abstract method called after a drag/drop object is clicked
55027      * and the drag or mousedown time thresholds have beeen met.
55028      * @param {Number} X click location
55029      * @param {Number} Y click location
55030      */
55031     startDrag: function(x, y) { /* override this */ },
55032
55033     /**
55034      * Code that executes immediately before the onDrag event
55035      * @private
55036      */
55037     b4Drag: function(e) { },
55038
55039     /**
55040      * Abstract method called during the onMouseMove event while dragging an
55041      * object.
55042      * @param {Event} e the mousemove event
55043      */
55044     onDrag: function(e) { /* override this */ },
55045
55046     /**
55047      * Abstract method called when this element fist begins hovering over
55048      * another DragDrop obj
55049      * @param {Event} e the mousemove event
55050      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
55051      * id this is hovering over.  In INTERSECT mode, an array of one or more
55052      * dragdrop items being hovered over.
55053      */
55054     onDragEnter: function(e, id) { /* override this */ },
55055
55056     /**
55057      * Code that executes immediately before the onDragOver event
55058      * @private
55059      */
55060     b4DragOver: function(e) { },
55061
55062     /**
55063      * Abstract method called when this element is hovering over another
55064      * DragDrop obj
55065      * @param {Event} e the mousemove event
55066      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
55067      * id this is hovering over.  In INTERSECT mode, an array of dd items
55068      * being hovered over.
55069      */
55070     onDragOver: function(e, id) { /* override this */ },
55071
55072     /**
55073      * Code that executes immediately before the onDragOut event
55074      * @private
55075      */
55076     b4DragOut: function(e) { },
55077
55078     /**
55079      * Abstract method called when we are no longer hovering over an element
55080      * @param {Event} e the mousemove event
55081      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
55082      * id this was hovering over.  In INTERSECT mode, an array of dd items
55083      * that the mouse is no longer over.
55084      */
55085     onDragOut: function(e, id) { /* override this */ },
55086
55087     /**
55088      * Code that executes immediately before the onDragDrop event
55089      * @private
55090      */
55091     b4DragDrop: function(e) { },
55092
55093     /**
55094      * Abstract method called when this item is dropped on another DragDrop
55095      * obj
55096      * @param {Event} e the mouseup event
55097      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
55098      * id this was dropped on.  In INTERSECT mode, an array of dd items this
55099      * was dropped on.
55100      */
55101     onDragDrop: function(e, id) { /* override this */ },
55102
55103     /**
55104      * Abstract method called when this item is dropped on an area with no
55105      * drop target
55106      * @param {Event} e the mouseup event
55107      */
55108     onInvalidDrop: function(e) { /* override this */ },
55109
55110     /**
55111      * Code that executes immediately before the endDrag event
55112      * @private
55113      */
55114     b4EndDrag: function(e) { },
55115
55116     /**
55117      * Called when we are done dragging the object
55118      * @param {Event} e the mouseup event
55119      */
55120     endDrag: function(e) { /* override this */ },
55121
55122     /**
55123      * Code executed immediately before the onMouseDown event
55124      * @param {Event} e the mousedown event
55125      * @private
55126      */
55127     b4MouseDown: function(e) {  },
55128
55129     /**
55130      * Called when a drag/drop obj gets a mousedown
55131      * @param {Event} e the mousedown event
55132      */
55133     onMouseDown: function(e) { /* override this */ },
55134
55135     /**
55136      * Called when a drag/drop obj gets a mouseup
55137      * @param {Event} e the mouseup event
55138      */
55139     onMouseUp: function(e) { /* override this */ },
55140
55141     /**
55142      * Override the onAvailable method to do what is needed after the initial
55143      * position was determined.
55144      */
55145     onAvailable: function () {
55146     },
55147
55148     /**
55149      * @property {Object} defaultPadding
55150      * Provides default constraint padding to "constrainTo" elements.
55151      */
55152     defaultPadding: {
55153         left: 0,
55154         right: 0,
55155         top: 0,
55156         bottom: 0
55157     },
55158
55159     /**
55160      * Initializes the drag drop object's constraints to restrict movement to a certain element.
55161      *
55162      * Usage:
55163      *
55164      *     var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
55165      *                    { dragElId: "existingProxyDiv" });
55166      *     dd.startDrag = function(){
55167      *         this.constrainTo("parent-id");
55168      *     };
55169      *
55170      * Or you can initalize it using the {@link Ext.Element} object:
55171      *
55172      *     Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
55173      *         startDrag : function(){
55174      *             this.constrainTo("parent-id");
55175      *         }
55176      *     });
55177      *
55178      * @param {String/HTMLElement/Ext.Element} constrainTo The element or element ID to constrain to.
55179      * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
55180      * and can be either a number for symmetrical padding (4 would be equal to `{left:4, right:4, top:4, bottom:4}`) or
55181      * an object containing the sides to pad. For example: `{right:10, bottom:10}`
55182      * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
55183      */
55184     constrainTo : function(constrainTo, pad, inContent){
55185         if(Ext.isNumber(pad)){
55186             pad = {left: pad, right:pad, top:pad, bottom:pad};
55187         }
55188         pad = pad || this.defaultPadding;
55189         var b = Ext.get(this.getEl()).getBox(),
55190             ce = Ext.get(constrainTo),
55191             s = ce.getScroll(),
55192             c,
55193             cd = ce.dom;
55194         if(cd == document.body){
55195             c = { x: s.left, y: s.top, width: Ext.Element.getViewWidth(), height: Ext.Element.getViewHeight()};
55196         }else{
55197             var xy = ce.getXY();
55198             c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
55199         }
55200
55201
55202         var topSpace = b.y - c.y,
55203             leftSpace = b.x - c.x;
55204
55205         this.resetConstraints();
55206         this.setXConstraint(leftSpace - (pad.left||0), // left
55207                 c.width - leftSpace - b.width - (pad.right||0), //right
55208                                 this.xTickSize
55209         );
55210         this.setYConstraint(topSpace - (pad.top||0), //top
55211                 c.height - topSpace - b.height - (pad.bottom||0), //bottom
55212                                 this.yTickSize
55213         );
55214     },
55215
55216     /**
55217      * Returns a reference to the linked element
55218      * @return {HTMLElement} the html element
55219      */
55220     getEl: function() {
55221         if (!this._domRef) {
55222             this._domRef = Ext.getDom(this.id);
55223         }
55224
55225         return this._domRef;
55226     },
55227
55228     /**
55229      * Returns a reference to the actual element to drag.  By default this is
55230      * the same as the html element, but it can be assigned to another
55231      * element. An example of this can be found in Ext.dd.DDProxy
55232      * @return {HTMLElement} the html element
55233      */
55234     getDragEl: function() {
55235         return Ext.getDom(this.dragElId);
55236     },
55237
55238     /**
55239      * Sets up the DragDrop object.  Must be called in the constructor of any
55240      * Ext.dd.DragDrop subclass
55241      * @param {String} id the id of the linked element
55242      * @param {String} sGroup the group of related items
55243      * @param {Object} config configuration attributes
55244      */
55245     init: function(id, sGroup, config) {
55246         this.initTarget(id, sGroup, config);
55247         Ext.EventManager.on(this.id, "mousedown", this.handleMouseDown, this);
55248         // Ext.EventManager.on(this.id, "selectstart", Event.preventDefault);
55249     },
55250
55251     /**
55252      * Initializes Targeting functionality only... the object does not
55253      * get a mousedown handler.
55254      * @param {String} id the id of the linked element
55255      * @param {String} sGroup the group of related items
55256      * @param {Object} config configuration attributes
55257      */
55258     initTarget: function(id, sGroup, config) {
55259         // configuration attributes
55260         this.config = config || {};
55261
55262         // create a local reference to the drag and drop manager
55263         this.DDMInstance = Ext.dd.DragDropManager;
55264         // initialize the groups array
55265         this.groups = {};
55266
55267         // assume that we have an element reference instead of an id if the
55268         // parameter is not a string
55269         if (typeof id !== "string") {
55270             id = Ext.id(id);
55271         }
55272
55273         // set the id
55274         this.id = id;
55275
55276         // add to an interaction group
55277         this.addToGroup((sGroup) ? sGroup : "default");
55278
55279         // We don't want to register this as the handle with the manager
55280         // so we just set the id rather than calling the setter.
55281         this.handleElId = id;
55282
55283         // the linked element is the element that gets dragged by default
55284         this.setDragElId(id);
55285
55286         // by default, clicked anchors will not start drag operations.
55287         this.invalidHandleTypes = { A: "A" };
55288         this.invalidHandleIds = {};
55289         this.invalidHandleClasses = [];
55290
55291         this.applyConfig();
55292
55293         this.handleOnAvailable();
55294     },
55295
55296     /**
55297      * Applies the configuration parameters that were passed into the constructor.
55298      * This is supposed to happen at each level through the inheritance chain.  So
55299      * a DDProxy implentation will execute apply config on DDProxy, DD, and
55300      * DragDrop in order to get all of the parameters that are available in
55301      * each object.
55302      */
55303     applyConfig: function() {
55304
55305         // configurable properties:
55306         //    padding, isTarget, maintainOffset, primaryButtonOnly
55307         this.padding           = this.config.padding || [0, 0, 0, 0];
55308         this.isTarget          = (this.config.isTarget !== false);
55309         this.maintainOffset    = (this.config.maintainOffset);
55310         this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
55311
55312     },
55313
55314     /**
55315      * Executed when the linked element is available
55316      * @private
55317      */
55318     handleOnAvailable: function() {
55319         this.available = true;
55320         this.resetConstraints();
55321         this.onAvailable();
55322     },
55323
55324     /**
55325      * Configures the padding for the target zone in px.  Effectively expands
55326      * (or reduces) the virtual object size for targeting calculations.
55327      * Supports css-style shorthand; if only one parameter is passed, all sides
55328      * will have that padding, and if only two are passed, the top and bottom
55329      * will have the first param, the left and right the second.
55330      * @param {Number} iTop    Top pad
55331      * @param {Number} iRight  Right pad
55332      * @param {Number} iBot    Bot pad
55333      * @param {Number} iLeft   Left pad
55334      */
55335     setPadding: function(iTop, iRight, iBot, iLeft) {
55336         // this.padding = [iLeft, iRight, iTop, iBot];
55337         if (!iRight && 0 !== iRight) {
55338             this.padding = [iTop, iTop, iTop, iTop];
55339         } else if (!iBot && 0 !== iBot) {
55340             this.padding = [iTop, iRight, iTop, iRight];
55341         } else {
55342             this.padding = [iTop, iRight, iBot, iLeft];
55343         }
55344     },
55345
55346     /**
55347      * Stores the initial placement of the linked element.
55348      * @param {Number} diffX   the X offset, default 0
55349      * @param {Number} diffY   the Y offset, default 0
55350      */
55351     setInitPosition: function(diffX, diffY) {
55352         var el = this.getEl();
55353
55354         if (!this.DDMInstance.verifyEl(el)) {
55355             return;
55356         }
55357
55358         var dx = diffX || 0;
55359         var dy = diffY || 0;
55360
55361         var p = Ext.Element.getXY( el );
55362
55363         this.initPageX = p[0] - dx;
55364         this.initPageY = p[1] - dy;
55365
55366         this.lastPageX = p[0];
55367         this.lastPageY = p[1];
55368
55369         this.setStartPosition(p);
55370     },
55371
55372     /**
55373      * Sets the start position of the element.  This is set when the obj
55374      * is initialized, the reset when a drag is started.
55375      * @param pos current position (from previous lookup)
55376      * @private
55377      */
55378     setStartPosition: function(pos) {
55379         var p = pos || Ext.Element.getXY( this.getEl() );
55380         this.deltaSetXY = null;
55381
55382         this.startPageX = p[0];
55383         this.startPageY = p[1];
55384     },
55385
55386     /**
55387      * Adds this instance to a group of related drag/drop objects.  All
55388      * instances belong to at least one group, and can belong to as many
55389      * groups as needed.
55390      * @param {String} sGroup the name of the group
55391      */
55392     addToGroup: function(sGroup) {
55393         this.groups[sGroup] = true;
55394         this.DDMInstance.regDragDrop(this, sGroup);
55395     },
55396
55397     /**
55398      * Removes this instance from the supplied interaction group
55399      * @param {String} sGroup  The group to drop
55400      */
55401     removeFromGroup: function(sGroup) {
55402         if (this.groups[sGroup]) {
55403             delete this.groups[sGroup];
55404         }
55405
55406         this.DDMInstance.removeDDFromGroup(this, sGroup);
55407     },
55408
55409     /**
55410      * Allows you to specify that an element other than the linked element
55411      * will be moved with the cursor during a drag
55412      * @param {String} id the id of the element that will be used to initiate the drag
55413      */
55414     setDragElId: function(id) {
55415         this.dragElId = id;
55416     },
55417
55418     /**
55419      * Allows you to specify a child of the linked element that should be
55420      * used to initiate the drag operation.  An example of this would be if
55421      * you have a content div with text and links.  Clicking anywhere in the
55422      * content area would normally start the drag operation.  Use this method
55423      * to specify that an element inside of the content div is the element
55424      * that starts the drag operation.
55425      * @param {String} id the id of the element that will be used to
55426      * initiate the drag.
55427      */
55428     setHandleElId: function(id) {
55429         if (typeof id !== "string") {
55430             id = Ext.id(id);
55431         }
55432         this.handleElId = id;
55433         this.DDMInstance.regHandle(this.id, id);
55434     },
55435
55436     /**
55437      * Allows you to set an element outside of the linked element as a drag
55438      * handle
55439      * @param {String} id the id of the element that will be used to initiate the drag
55440      */
55441     setOuterHandleElId: function(id) {
55442         if (typeof id !== "string") {
55443             id = Ext.id(id);
55444         }
55445         Ext.EventManager.on(id, "mousedown", this.handleMouseDown, this);
55446         this.setHandleElId(id);
55447
55448         this.hasOuterHandles = true;
55449     },
55450
55451     /**
55452      * Removes all drag and drop hooks for this element
55453      */
55454     unreg: function() {
55455         Ext.EventManager.un(this.id, "mousedown", this.handleMouseDown, this);
55456         this._domRef = null;
55457         this.DDMInstance._remove(this);
55458     },
55459
55460     destroy : function(){
55461         this.unreg();
55462     },
55463
55464     /**
55465      * Returns true if this instance is locked, or the drag drop mgr is locked
55466      * (meaning that all drag/drop is disabled on the page.)
55467      * @return {Boolean} true if this obj or all drag/drop is locked, else
55468      * false
55469      */
55470     isLocked: function() {
55471         return (this.DDMInstance.isLocked() || this.locked);
55472     },
55473
55474     /**
55475      * Called when this object is clicked
55476      * @param {Event} e
55477      * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
55478      * @private
55479      */
55480     handleMouseDown: function(e, oDD){
55481         if (this.primaryButtonOnly && e.button != 0) {
55482             return;
55483         }
55484
55485         if (this.isLocked()) {
55486             return;
55487         }
55488
55489         this.DDMInstance.refreshCache(this.groups);
55490
55491         var pt = e.getPoint();
55492         if (!this.hasOuterHandles && !this.DDMInstance.isOverTarget(pt, this) )  {
55493         } else {
55494             if (this.clickValidator(e)) {
55495                 // set the initial element position
55496                 this.setStartPosition();
55497                 this.b4MouseDown(e);
55498                 this.onMouseDown(e);
55499
55500                 this.DDMInstance.handleMouseDown(e, this);
55501
55502                 this.DDMInstance.stopEvent(e);
55503             } else {
55504
55505
55506             }
55507         }
55508     },
55509
55510     clickValidator: function(e) {
55511         var target = e.getTarget();
55512         return ( this.isValidHandleChild(target) &&
55513                     (this.id == this.handleElId ||
55514                         this.DDMInstance.handleWasClicked(target, this.id)) );
55515     },
55516
55517     /**
55518      * Allows you to specify a tag name that should not start a drag operation
55519      * when clicked.  This is designed to facilitate embedding links within a
55520      * drag handle that do something other than start the drag.
55521      * @method addInvalidHandleType
55522      * @param {String} tagName the type of element to exclude
55523      */
55524     addInvalidHandleType: function(tagName) {
55525         var type = tagName.toUpperCase();
55526         this.invalidHandleTypes[type] = type;
55527     },
55528
55529     /**
55530      * Lets you to specify an element id for a child of a drag handle
55531      * that should not initiate a drag
55532      * @method addInvalidHandleId
55533      * @param {String} id the element id of the element you wish to ignore
55534      */
55535     addInvalidHandleId: function(id) {
55536         if (typeof id !== "string") {
55537             id = Ext.id(id);
55538         }
55539         this.invalidHandleIds[id] = id;
55540     },
55541
55542     /**
55543      * Lets you specify a css class of elements that will not initiate a drag
55544      * @param {String} cssClass the class of the elements you wish to ignore
55545      */
55546     addInvalidHandleClass: function(cssClass) {
55547         this.invalidHandleClasses.push(cssClass);
55548     },
55549
55550     /**
55551      * Unsets an excluded tag name set by addInvalidHandleType
55552      * @param {String} tagName the type of element to unexclude
55553      */
55554     removeInvalidHandleType: function(tagName) {
55555         var type = tagName.toUpperCase();
55556         // this.invalidHandleTypes[type] = null;
55557         delete this.invalidHandleTypes[type];
55558     },
55559
55560     /**
55561      * Unsets an invalid handle id
55562      * @param {String} id the id of the element to re-enable
55563      */
55564     removeInvalidHandleId: function(id) {
55565         if (typeof id !== "string") {
55566             id = Ext.id(id);
55567         }
55568         delete this.invalidHandleIds[id];
55569     },
55570
55571     /**
55572      * Unsets an invalid css class
55573      * @param {String} cssClass the class of the element(s) you wish to
55574      * re-enable
55575      */
55576     removeInvalidHandleClass: function(cssClass) {
55577         for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
55578             if (this.invalidHandleClasses[i] == cssClass) {
55579                 delete this.invalidHandleClasses[i];
55580             }
55581         }
55582     },
55583
55584     /**
55585      * Checks the tag exclusion list to see if this click should be ignored
55586      * @param {HTMLElement} node the HTMLElement to evaluate
55587      * @return {Boolean} true if this is a valid tag type, false if not
55588      */
55589     isValidHandleChild: function(node) {
55590
55591         var valid = true;
55592         // var n = (node.nodeName == "#text") ? node.parentNode : node;
55593         var nodeName;
55594         try {
55595             nodeName = node.nodeName.toUpperCase();
55596         } catch(e) {
55597             nodeName = node.nodeName;
55598         }
55599         valid = valid && !this.invalidHandleTypes[nodeName];
55600         valid = valid && !this.invalidHandleIds[node.id];
55601
55602         for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
55603             valid = !Ext.fly(node).hasCls(this.invalidHandleClasses[i]);
55604         }
55605
55606
55607         return valid;
55608
55609     },
55610
55611     /**
55612      * Creates the array of horizontal tick marks if an interval was specified
55613      * in setXConstraint().
55614      * @private
55615      */
55616     setXTicks: function(iStartX, iTickSize) {
55617         this.xTicks = [];
55618         this.xTickSize = iTickSize;
55619
55620         var tickMap = {};
55621
55622         for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
55623             if (!tickMap[i]) {
55624                 this.xTicks[this.xTicks.length] = i;
55625                 tickMap[i] = true;
55626             }
55627         }
55628
55629         for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
55630             if (!tickMap[i]) {
55631                 this.xTicks[this.xTicks.length] = i;
55632                 tickMap[i] = true;
55633             }
55634         }
55635
55636         Ext.Array.sort(this.xTicks, this.DDMInstance.numericSort);
55637     },
55638
55639     /**
55640      * Creates the array of vertical tick marks if an interval was specified in
55641      * setYConstraint().
55642      * @private
55643      */
55644     setYTicks: function(iStartY, iTickSize) {
55645         this.yTicks = [];
55646         this.yTickSize = iTickSize;
55647
55648         var tickMap = {};
55649
55650         for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
55651             if (!tickMap[i]) {
55652                 this.yTicks[this.yTicks.length] = i;
55653                 tickMap[i] = true;
55654             }
55655         }
55656
55657         for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
55658             if (!tickMap[i]) {
55659                 this.yTicks[this.yTicks.length] = i;
55660                 tickMap[i] = true;
55661             }
55662         }
55663
55664         Ext.Array.sort(this.yTicks, this.DDMInstance.numericSort);
55665     },
55666
55667     /**
55668      * By default, the element can be dragged any place on the screen.  Use
55669      * this method to limit the horizontal travel of the element.  Pass in
55670      * 0,0 for the parameters if you want to lock the drag to the y axis.
55671      * @param {Number} iLeft the number of pixels the element can move to the left
55672      * @param {Number} iRight the number of pixels the element can move to the
55673      * right
55674      * @param {Number} iTickSize (optional) parameter for specifying that the
55675      * element should move iTickSize pixels at a time.
55676      */
55677     setXConstraint: function(iLeft, iRight, iTickSize) {
55678         this.leftConstraint = iLeft;
55679         this.rightConstraint = iRight;
55680
55681         this.minX = this.initPageX - iLeft;
55682         this.maxX = this.initPageX + iRight;
55683         if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
55684
55685         this.constrainX = true;
55686     },
55687
55688     /**
55689      * Clears any constraints applied to this instance.  Also clears ticks
55690      * since they can't exist independent of a constraint at this time.
55691      */
55692     clearConstraints: function() {
55693         this.constrainX = false;
55694         this.constrainY = false;
55695         this.clearTicks();
55696     },
55697
55698     /**
55699      * Clears any tick interval defined for this instance
55700      */
55701     clearTicks: function() {
55702         this.xTicks = null;
55703         this.yTicks = null;
55704         this.xTickSize = 0;
55705         this.yTickSize = 0;
55706     },
55707
55708     /**
55709      * By default, the element can be dragged any place on the screen.  Set
55710      * this to limit the vertical travel of the element.  Pass in 0,0 for the
55711      * parameters if you want to lock the drag to the x axis.
55712      * @param {Number} iUp the number of pixels the element can move up
55713      * @param {Number} iDown the number of pixels the element can move down
55714      * @param {Number} iTickSize (optional) parameter for specifying that the
55715      * element should move iTickSize pixels at a time.
55716      */
55717     setYConstraint: function(iUp, iDown, iTickSize) {
55718         this.topConstraint = iUp;
55719         this.bottomConstraint = iDown;
55720
55721         this.minY = this.initPageY - iUp;
55722         this.maxY = this.initPageY + iDown;
55723         if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
55724
55725         this.constrainY = true;
55726
55727     },
55728
55729     /**
55730      * Must be called if you manually reposition a dd element.
55731      * @param {Boolean} maintainOffset
55732      */
55733     resetConstraints: function() {
55734         // Maintain offsets if necessary
55735         if (this.initPageX || this.initPageX === 0) {
55736             // figure out how much this thing has moved
55737             var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
55738             var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
55739
55740             this.setInitPosition(dx, dy);
55741
55742         // This is the first time we have detected the element's position
55743         } else {
55744             this.setInitPosition();
55745         }
55746
55747         if (this.constrainX) {
55748             this.setXConstraint( this.leftConstraint,
55749                                  this.rightConstraint,
55750                                  this.xTickSize        );
55751         }
55752
55753         if (this.constrainY) {
55754             this.setYConstraint( this.topConstraint,
55755                                  this.bottomConstraint,
55756                                  this.yTickSize         );
55757         }
55758     },
55759
55760     /**
55761      * Normally the drag element is moved pixel by pixel, but we can specify
55762      * that it move a number of pixels at a time.  This method resolves the
55763      * location when we have it set up like this.
55764      * @param {Number} val where we want to place the object
55765      * @param {Number[]} tickArray sorted array of valid points
55766      * @return {Number} the closest tick
55767      * @private
55768      */
55769     getTick: function(val, tickArray) {
55770         if (!tickArray) {
55771             // If tick interval is not defined, it is effectively 1 pixel,
55772             // so we return the value passed to us.
55773             return val;
55774         } else if (tickArray[0] >= val) {
55775             // The value is lower than the first tick, so we return the first
55776             // tick.
55777             return tickArray[0];
55778         } else {
55779             for (var i=0, len=tickArray.length; i<len; ++i) {
55780                 var next = i + 1;
55781                 if (tickArray[next] && tickArray[next] >= val) {
55782                     var diff1 = val - tickArray[i];
55783                     var diff2 = tickArray[next] - val;
55784                     return (diff2 > diff1) ? tickArray[i] : tickArray[next];
55785                 }
55786             }
55787
55788             // The value is larger than the last tick, so we return the last
55789             // tick.
55790             return tickArray[tickArray.length - 1];
55791         }
55792     },
55793
55794     /**
55795      * toString method
55796      * @return {String} string representation of the dd obj
55797      */
55798     toString: function() {
55799         return ("DragDrop " + this.id);
55800     }
55801
55802 });
55803
55804 /*
55805  * This is a derivative of the similarly named class in the YUI Library.
55806  * The original license:
55807  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
55808  * Code licensed under the BSD License:
55809  * http://developer.yahoo.net/yui/license.txt
55810  */
55811
55812
55813 /**
55814  * @class Ext.dd.DD
55815  * A DragDrop implementation where the linked element follows the
55816  * mouse cursor during a drag.
55817  * @extends Ext.dd.DragDrop
55818  */
55819 Ext.define('Ext.dd.DD', {
55820     extend: 'Ext.dd.DragDrop',
55821     requires: ['Ext.dd.DragDropManager'],
55822
55823     /**
55824      * Creates new DD instance.
55825      * @param {String} id the id of the linked element
55826      * @param {String} sGroup the group of related DragDrop items
55827      * @param {Object} config an object containing configurable attributes.
55828      * Valid properties for DD: scroll
55829      */
55830     constructor: function(id, sGroup, config) {
55831         if (id) {
55832             this.init(id, sGroup, config);
55833         }
55834     },
55835
55836     /**
55837      * When set to true, the utility automatically tries to scroll the browser
55838      * window when a drag and drop element is dragged near the viewport boundary.
55839      * Defaults to true.
55840      * @property scroll
55841      * @type Boolean
55842      */
55843     scroll: true,
55844
55845     /**
55846      * Sets the pointer offset to the distance between the linked element's top
55847      * left corner and the location the element was clicked
55848      * @method autoOffset
55849      * @param {Number} iPageX the X coordinate of the click
55850      * @param {Number} iPageY the Y coordinate of the click
55851      */
55852     autoOffset: function(iPageX, iPageY) {
55853         var x = iPageX - this.startPageX;
55854         var y = iPageY - this.startPageY;
55855         this.setDelta(x, y);
55856     },
55857
55858     /**
55859      * Sets the pointer offset.  You can call this directly to force the
55860      * offset to be in a particular location (e.g., pass in 0,0 to set it
55861      * to the center of the object)
55862      * @method setDelta
55863      * @param {Number} iDeltaX the distance from the left
55864      * @param {Number} iDeltaY the distance from the top
55865      */
55866     setDelta: function(iDeltaX, iDeltaY) {
55867         this.deltaX = iDeltaX;
55868         this.deltaY = iDeltaY;
55869     },
55870
55871     /**
55872      * Sets the drag element to the location of the mousedown or click event,
55873      * maintaining the cursor location relative to the location on the element
55874      * that was clicked.  Override this if you want to place the element in a
55875      * location other than where the cursor is.
55876      * @method setDragElPos
55877      * @param {Number} iPageX the X coordinate of the mousedown or drag event
55878      * @param {Number} iPageY the Y coordinate of the mousedown or drag event
55879      */
55880     setDragElPos: function(iPageX, iPageY) {
55881         // the first time we do this, we are going to check to make sure
55882         // the element has css positioning
55883
55884         var el = this.getDragEl();
55885         this.alignElWithMouse(el, iPageX, iPageY);
55886     },
55887
55888     /**
55889      * Sets the element to the location of the mousedown or click event,
55890      * maintaining the cursor location relative to the location on the element
55891      * that was clicked.  Override this if you want to place the element in a
55892      * location other than where the cursor is.
55893      * @method alignElWithMouse
55894      * @param {HTMLElement} el the element to move
55895      * @param {Number} iPageX the X coordinate of the mousedown or drag event
55896      * @param {Number} iPageY the Y coordinate of the mousedown or drag event
55897      */
55898     alignElWithMouse: function(el, iPageX, iPageY) {
55899         var oCoord = this.getTargetCoord(iPageX, iPageY),
55900             fly = el.dom ? el : Ext.fly(el, '_dd'),
55901             elSize = fly.getSize(),
55902             EL = Ext.Element,
55903             vpSize;
55904
55905         if (!this.deltaSetXY) {
55906             vpSize = this.cachedViewportSize = { width: EL.getDocumentWidth(), height: EL.getDocumentHeight() };
55907             var aCoord = [
55908                 Math.max(0, Math.min(oCoord.x, vpSize.width - elSize.width)),
55909                 Math.max(0, Math.min(oCoord.y, vpSize.height - elSize.height))
55910             ];
55911             fly.setXY(aCoord);
55912             var newLeft = fly.getLeft(true);
55913             var newTop  = fly.getTop(true);
55914             this.deltaSetXY = [newLeft - oCoord.x, newTop - oCoord.y];
55915         } else {
55916             vpSize = this.cachedViewportSize;
55917             fly.setLeftTop(
55918                 Math.max(0, Math.min(oCoord.x + this.deltaSetXY[0], vpSize.width - elSize.width)),
55919                 Math.max(0, Math.min(oCoord.y + this.deltaSetXY[1], vpSize.height - elSize.height))
55920             );
55921         }
55922
55923         this.cachePosition(oCoord.x, oCoord.y);
55924         this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
55925         return oCoord;
55926     },
55927
55928     /**
55929      * Saves the most recent position so that we can reset the constraints and
55930      * tick marks on-demand.  We need to know this so that we can calculate the
55931      * number of pixels the element is offset from its original position.
55932      * @method cachePosition
55933      * @param {Number} iPageX (optional) the current x position (this just makes it so we
55934      * don't have to look it up again)
55935      * @param {Number} iPageY (optional) the current y position (this just makes it so we
55936      * don't have to look it up again)
55937      */
55938     cachePosition: function(iPageX, iPageY) {
55939         if (iPageX) {
55940             this.lastPageX = iPageX;
55941             this.lastPageY = iPageY;
55942         } else {
55943             var aCoord = Ext.Element.getXY(this.getEl());
55944             this.lastPageX = aCoord[0];
55945             this.lastPageY = aCoord[1];
55946         }
55947     },
55948
55949     /**
55950      * Auto-scroll the window if the dragged object has been moved beyond the
55951      * visible window boundary.
55952      * @method autoScroll
55953      * @param {Number} x the drag element's x position
55954      * @param {Number} y the drag element's y position
55955      * @param {Number} h the height of the drag element
55956      * @param {Number} w the width of the drag element
55957      * @private
55958      */
55959     autoScroll: function(x, y, h, w) {
55960
55961         if (this.scroll) {
55962             // The client height
55963             var clientH = Ext.Element.getViewHeight();
55964
55965             // The client width
55966             var clientW = Ext.Element.getViewWidth();
55967
55968             // The amt scrolled down
55969             var st = this.DDMInstance.getScrollTop();
55970
55971             // The amt scrolled right
55972             var sl = this.DDMInstance.getScrollLeft();
55973
55974             // Location of the bottom of the element
55975             var bot = h + y;
55976
55977             // Location of the right of the element
55978             var right = w + x;
55979
55980             // The distance from the cursor to the bottom of the visible area,
55981             // adjusted so that we don't scroll if the cursor is beyond the
55982             // element drag constraints
55983             var toBot = (clientH + st - y - this.deltaY);
55984
55985             // The distance from the cursor to the right of the visible area
55986             var toRight = (clientW + sl - x - this.deltaX);
55987
55988
55989             // How close to the edge the cursor must be before we scroll
55990             // var thresh = (document.all) ? 100 : 40;
55991             var thresh = 40;
55992
55993             // How many pixels to scroll per autoscroll op.  This helps to reduce
55994             // clunky scrolling. IE is more sensitive about this ... it needs this
55995             // value to be higher.
55996             var scrAmt = (document.all) ? 80 : 30;
55997
55998             // Scroll down if we are near the bottom of the visible page and the
55999             // obj extends below the crease
56000             if ( bot > clientH && toBot < thresh ) {
56001                 window.scrollTo(sl, st + scrAmt);
56002             }
56003
56004             // Scroll up if the window is scrolled down and the top of the object
56005             // goes above the top border
56006             if ( y < st && st > 0 && y - st < thresh ) {
56007                 window.scrollTo(sl, st - scrAmt);
56008             }
56009
56010             // Scroll right if the obj is beyond the right border and the cursor is
56011             // near the border.
56012             if ( right > clientW && toRight < thresh ) {
56013                 window.scrollTo(sl + scrAmt, st);
56014             }
56015
56016             // Scroll left if the window has been scrolled to the right and the obj
56017             // extends past the left border
56018             if ( x < sl && sl > 0 && x - sl < thresh ) {
56019                 window.scrollTo(sl - scrAmt, st);
56020             }
56021         }
56022     },
56023
56024     /**
56025      * Finds the location the element should be placed if we want to move
56026      * it to where the mouse location less the click offset would place us.
56027      * @method getTargetCoord
56028      * @param {Number} iPageX the X coordinate of the click
56029      * @param {Number} iPageY the Y coordinate of the click
56030      * @return an object that contains the coordinates (Object.x and Object.y)
56031      * @private
56032      */
56033     getTargetCoord: function(iPageX, iPageY) {
56034         var x = iPageX - this.deltaX;
56035         var y = iPageY - this.deltaY;
56036
56037         if (this.constrainX) {
56038             if (x < this.minX) {
56039                 x = this.minX;
56040             }
56041             if (x > this.maxX) {
56042                 x = this.maxX;
56043             }
56044         }
56045
56046         if (this.constrainY) {
56047             if (y < this.minY) {
56048                 y = this.minY;
56049             }
56050             if (y > this.maxY) {
56051                 y = this.maxY;
56052             }
56053         }
56054
56055         x = this.getTick(x, this.xTicks);
56056         y = this.getTick(y, this.yTicks);
56057
56058
56059         return {x: x, y: y};
56060     },
56061
56062     /**
56063      * Sets up config options specific to this class. Overrides
56064      * Ext.dd.DragDrop, but all versions of this method through the
56065      * inheritance chain are called
56066      */
56067     applyConfig: function() {
56068         this.callParent();
56069         this.scroll = (this.config.scroll !== false);
56070     },
56071
56072     /**
56073      * Event that fires prior to the onMouseDown event.  Overrides
56074      * Ext.dd.DragDrop.
56075      */
56076     b4MouseDown: function(e) {
56077         // this.resetConstraints();
56078         this.autoOffset(e.getPageX(), e.getPageY());
56079     },
56080
56081     /**
56082      * Event that fires prior to the onDrag event.  Overrides
56083      * Ext.dd.DragDrop.
56084      */
56085     b4Drag: function(e) {
56086         this.setDragElPos(e.getPageX(), e.getPageY());
56087     },
56088
56089     toString: function() {
56090         return ("DD " + this.id);
56091     }
56092
56093     //////////////////////////////////////////////////////////////////////////
56094     // Debugging ygDragDrop events that can be overridden
56095     //////////////////////////////////////////////////////////////////////////
56096     /*
56097     startDrag: function(x, y) {
56098     },
56099
56100     onDrag: function(e) {
56101     },
56102
56103     onDragEnter: function(e, id) {
56104     },
56105
56106     onDragOver: function(e, id) {
56107     },
56108
56109     onDragOut: function(e, id) {
56110     },
56111
56112     onDragDrop: function(e, id) {
56113     },
56114
56115     endDrag: function(e) {
56116     }
56117
56118     */
56119
56120 });
56121
56122 /*
56123  * This is a derivative of the similarly named class in the YUI Library.
56124  * The original license:
56125  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
56126  * Code licensed under the BSD License:
56127  * http://developer.yahoo.net/yui/license.txt
56128  */
56129
56130 /**
56131  * @class Ext.dd.DDProxy
56132  * @extends Ext.dd.DD
56133  * A DragDrop implementation that inserts an empty, bordered div into
56134  * the document that follows the cursor during drag operations.  At the time of
56135  * the click, the frame div is resized to the dimensions of the linked html
56136  * element, and moved to the exact location of the linked element.
56137  *
56138  * References to the "frame" element refer to the single proxy element that
56139  * was created to be dragged in place of all DDProxy elements on the
56140  * page.
56141  */
56142 Ext.define('Ext.dd.DDProxy', {
56143     extend: 'Ext.dd.DD',
56144
56145     statics: {
56146         /**
56147          * The default drag frame div id
56148          * @static
56149          */
56150         dragElId: "ygddfdiv"
56151     },
56152
56153     /**
56154      * Creates new DDProxy.
56155      * @param {String} id the id of the linked html element
56156      * @param {String} sGroup the group of related DragDrop objects
56157      * @param {Object} config an object containing configurable attributes.
56158      * Valid properties for DDProxy in addition to those in DragDrop:
56159      * 
56160      * - resizeFrame
56161      * - centerFrame
56162      * - dragElId
56163      */
56164     constructor: function(id, sGroup, config) {
56165         if (id) {
56166             this.init(id, sGroup, config);
56167             this.initFrame();
56168         }
56169     },
56170
56171     /**
56172      * By default we resize the drag frame to be the same size as the element
56173      * we want to drag (this is to get the frame effect).  We can turn it off
56174      * if we want a different behavior.
56175      * @property resizeFrame
56176      * @type Boolean
56177      */
56178     resizeFrame: true,
56179
56180     /**
56181      * By default the frame is positioned exactly where the drag element is, so
56182      * we use the cursor offset provided by Ext.dd.DD.  Another option that works only if
56183      * you do not have constraints on the obj is to have the drag frame centered
56184      * around the cursor.  Set centerFrame to true for this effect.
56185      * @property centerFrame
56186      * @type Boolean
56187      */
56188     centerFrame: false,
56189
56190     /**
56191      * Creates the proxy element if it does not yet exist
56192      * @method createFrame
56193      */
56194     createFrame: function() {
56195         var self = this;
56196         var body = document.body;
56197
56198         if (!body || !body.firstChild) {
56199             setTimeout( function() { self.createFrame(); }, 50 );
56200             return;
56201         }
56202
56203         var div = this.getDragEl();
56204
56205         if (!div) {
56206             div    = document.createElement("div");
56207             div.id = this.dragElId;
56208             var s  = div.style;
56209
56210             s.position   = "absolute";
56211             s.visibility = "hidden";
56212             s.cursor     = "move";
56213             s.border     = "2px solid #aaa";
56214             s.zIndex     = 999;
56215
56216             // appendChild can blow up IE if invoked prior to the window load event
56217             // while rendering a table.  It is possible there are other scenarios
56218             // that would cause this to happen as well.
56219             body.insertBefore(div, body.firstChild);
56220         }
56221     },
56222
56223     /**
56224      * Initialization for the drag frame element.  Must be called in the
56225      * constructor of all subclasses
56226      * @method initFrame
56227      */
56228     initFrame: function() {
56229         this.createFrame();
56230     },
56231
56232     applyConfig: function() {
56233         this.callParent();
56234
56235         this.resizeFrame = (this.config.resizeFrame !== false);
56236         this.centerFrame = (this.config.centerFrame);
56237         this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
56238     },
56239
56240     /**
56241      * Resizes the drag frame to the dimensions of the clicked object, positions
56242      * it over the object, and finally displays it
56243      * @method showFrame
56244      * @param {Number} iPageX X click position
56245      * @param {Number} iPageY Y click position
56246      * @private
56247      */
56248     showFrame: function(iPageX, iPageY) {
56249         var el = this.getEl();
56250         var dragEl = this.getDragEl();
56251         var s = dragEl.style;
56252
56253         this._resizeProxy();
56254
56255         if (this.centerFrame) {
56256             this.setDelta( Math.round(parseInt(s.width,  10)/2),
56257                            Math.round(parseInt(s.height, 10)/2) );
56258         }
56259
56260         this.setDragElPos(iPageX, iPageY);
56261
56262         Ext.fly(dragEl).show();
56263     },
56264
56265     /**
56266      * The proxy is automatically resized to the dimensions of the linked
56267      * element when a drag is initiated, unless resizeFrame is set to false
56268      * @method _resizeProxy
56269      * @private
56270      */
56271     _resizeProxy: function() {
56272         if (this.resizeFrame) {
56273             var el = this.getEl();
56274             Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
56275         }
56276     },
56277
56278     // overrides Ext.dd.DragDrop
56279     b4MouseDown: function(e) {
56280         var x = e.getPageX();
56281         var y = e.getPageY();
56282         this.autoOffset(x, y);
56283         this.setDragElPos(x, y);
56284     },
56285
56286     // overrides Ext.dd.DragDrop
56287     b4StartDrag: function(x, y) {
56288         // show the drag frame
56289         this.showFrame(x, y);
56290     },
56291
56292     // overrides Ext.dd.DragDrop
56293     b4EndDrag: function(e) {
56294         Ext.fly(this.getDragEl()).hide();
56295     },
56296
56297     // overrides Ext.dd.DragDrop
56298     // By default we try to move the element to the last location of the frame.
56299     // This is so that the default behavior mirrors that of Ext.dd.DD.
56300     endDrag: function(e) {
56301
56302         var lel = this.getEl();
56303         var del = this.getDragEl();
56304
56305         // Show the drag frame briefly so we can get its position
56306         del.style.visibility = "";
56307
56308         this.beforeMove();
56309         // Hide the linked element before the move to get around a Safari
56310         // rendering bug.
56311         lel.style.visibility = "hidden";
56312         Ext.dd.DDM.moveToEl(lel, del);
56313         del.style.visibility = "hidden";
56314         lel.style.visibility = "";
56315
56316         this.afterDrag();
56317     },
56318
56319     beforeMove : function(){
56320
56321     },
56322
56323     afterDrag : function(){
56324
56325     },
56326
56327     toString: function() {
56328         return ("DDProxy " + this.id);
56329     }
56330
56331 });
56332
56333 /**
56334  * @class Ext.dd.DragSource
56335  * @extends Ext.dd.DDProxy
56336  * A simple class that provides the basic implementation needed to make any element draggable.
56337  */
56338 Ext.define('Ext.dd.DragSource', {
56339     extend: 'Ext.dd.DDProxy',
56340     requires: [
56341         'Ext.dd.StatusProxy',
56342         'Ext.dd.DragDropManager'
56343     ],
56344
56345     /**
56346      * @cfg {String} ddGroup
56347      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
56348      * interact with other drag drop objects in the same group.
56349      */
56350
56351     /**
56352      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
56353      * The CSS class returned to the drag source when drop is allowed.
56354      */
56355     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
56356     /**
56357      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
56358      * The CSS class returned to the drag source when drop is not allowed.
56359      */
56360     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
56361
56362     /**
56363      * @cfg {Boolean} animRepair
56364      * If true, animates the proxy element back to the position of the handle element used to trigger the drag.
56365      */
56366     animRepair: true,
56367
56368     /**
56369      * @cfg {String} repairHighlightColor
56370      * The color to use when visually highlighting the drag source in the afterRepair
56371      * method after a failed drop (defaults to light blue). The color must be a 6 digit hex value, without
56372      * a preceding '#'.
56373      */
56374     repairHighlightColor: 'c3daf9',
56375
56376     /**
56377      * Creates new drag-source.
56378      * @constructor
56379      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
56380      * @param {Object} config (optional) Config object.
56381      */
56382     constructor: function(el, config) {
56383         this.el = Ext.get(el);
56384         if(!this.dragData){
56385             this.dragData = {};
56386         }
56387
56388         Ext.apply(this, config);
56389
56390         if(!this.proxy){
56391             this.proxy = Ext.create('Ext.dd.StatusProxy', {
56392                 animRepair: this.animRepair
56393             });
56394         }
56395         this.callParent([this.el.dom, this.ddGroup || this.group,
56396               {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true}]);
56397
56398         this.dragging = false;
56399     },
56400
56401     /**
56402      * Returns the data object associated with this drag source
56403      * @return {Object} data An object containing arbitrary data
56404      */
56405     getDragData : function(e){
56406         return this.dragData;
56407     },
56408
56409     // private
56410     onDragEnter : function(e, id){
56411         var target = Ext.dd.DragDropManager.getDDById(id);
56412         this.cachedTarget = target;
56413         if (this.beforeDragEnter(target, e, id) !== false) {
56414             if (target.isNotifyTarget) {
56415                 var status = target.notifyEnter(this, e, this.dragData);
56416                 this.proxy.setStatus(status);
56417             } else {
56418                 this.proxy.setStatus(this.dropAllowed);
56419             }
56420
56421             if (this.afterDragEnter) {
56422                 /**
56423                  * An empty function by default, but provided so that you can perform a custom action
56424                  * when the dragged item enters the drop target by providing an implementation.
56425                  * @param {Ext.dd.DragDrop} target The drop target
56426                  * @param {Event} e The event object
56427                  * @param {String} id The id of the dragged element
56428                  * @method afterDragEnter
56429                  */
56430                 this.afterDragEnter(target, e, id);
56431             }
56432         }
56433     },
56434
56435     /**
56436      * An empty function by default, but provided so that you can perform a custom action
56437      * before the dragged item enters the drop target and optionally cancel the onDragEnter.
56438      * @param {Ext.dd.DragDrop} target The drop target
56439      * @param {Event} e The event object
56440      * @param {String} id The id of the dragged element
56441      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
56442      */
56443     beforeDragEnter: function(target, e, id) {
56444         return true;
56445     },
56446
56447     // private
56448     alignElWithMouse: function() {
56449         this.callParent(arguments);
56450         this.proxy.sync();
56451     },
56452
56453     // private
56454     onDragOver: function(e, id) {
56455         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
56456         if (this.beforeDragOver(target, e, id) !== false) {
56457             if(target.isNotifyTarget){
56458                 var status = target.notifyOver(this, e, this.dragData);
56459                 this.proxy.setStatus(status);
56460             }
56461
56462             if (this.afterDragOver) {
56463                 /**
56464                  * An empty function by default, but provided so that you can perform a custom action
56465                  * while the dragged item is over the drop target by providing an implementation.
56466                  * @param {Ext.dd.DragDrop} target The drop target
56467                  * @param {Event} e The event object
56468                  * @param {String} id The id of the dragged element
56469                  * @method afterDragOver
56470                  */
56471                 this.afterDragOver(target, e, id);
56472             }
56473         }
56474     },
56475
56476     /**
56477      * An empty function by default, but provided so that you can perform a custom action
56478      * while the dragged item is over the drop target and optionally cancel the onDragOver.
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      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
56483      */
56484     beforeDragOver: function(target, e, id) {
56485         return true;
56486     },
56487
56488     // private
56489     onDragOut: function(e, id) {
56490         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
56491         if (this.beforeDragOut(target, e, id) !== false) {
56492             if (target.isNotifyTarget) {
56493                 target.notifyOut(this, e, this.dragData);
56494             }
56495             this.proxy.reset();
56496             if (this.afterDragOut) {
56497                 /**
56498                  * An empty function by default, but provided so that you can perform a custom action
56499                  * after the dragged item is dragged out of the target without dropping.
56500                  * @param {Ext.dd.DragDrop} target The drop target
56501                  * @param {Event} e The event object
56502                  * @param {String} id The id of the dragged element
56503                  * @method afterDragOut
56504                  */
56505                 this.afterDragOut(target, e, id);
56506             }
56507         }
56508         this.cachedTarget = null;
56509     },
56510
56511     /**
56512      * An empty function by default, but provided so that you can perform a custom action before the dragged
56513      * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
56514      * @param {Ext.dd.DragDrop} target The drop target
56515      * @param {Event} e The event object
56516      * @param {String} id The id of the dragged element
56517      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
56518      */
56519     beforeDragOut: function(target, e, id){
56520         return true;
56521     },
56522
56523     // private
56524     onDragDrop: function(e, id){
56525         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
56526         if (this.beforeDragDrop(target, e, id) !== false) {
56527             if (target.isNotifyTarget) {
56528                 if (target.notifyDrop(this, e, this.dragData) !== false) { // valid drop?
56529                     this.onValidDrop(target, e, id);
56530                 } else {
56531                     this.onInvalidDrop(target, e, id);
56532                 }
56533             } else {
56534                 this.onValidDrop(target, e, id);
56535             }
56536
56537             if (this.afterDragDrop) {
56538                 /**
56539                  * An empty function by default, but provided so that you can perform a custom action
56540                  * after a valid drag drop has occurred by providing an implementation.
56541                  * @param {Ext.dd.DragDrop} target The drop target
56542                  * @param {Event} e The event object
56543                  * @param {String} id The id of the dropped element
56544                  * @method afterDragDrop
56545                  */
56546                 this.afterDragDrop(target, e, id);
56547             }
56548         }
56549         delete this.cachedTarget;
56550     },
56551
56552     /**
56553      * An empty function by default, but provided so that you can perform a custom action before the dragged
56554      * item is dropped onto the target and optionally cancel the onDragDrop.
56555      * @param {Ext.dd.DragDrop} target The drop target
56556      * @param {Event} e The event object
56557      * @param {String} id The id of the dragged element
56558      * @return {Boolean} isValid True if the drag drop event is valid, else false to cancel
56559      */
56560     beforeDragDrop: function(target, e, id){
56561         return true;
56562     },
56563
56564     // private
56565     onValidDrop: function(target, e, id){
56566         this.hideProxy();
56567         if(this.afterValidDrop){
56568             /**
56569              * An empty function by default, but provided so that you can perform a custom action
56570              * after a valid drop has occurred by providing an implementation.
56571              * @param {Object} target The target DD
56572              * @param {Event} e The event object
56573              * @param {String} id The id of the dropped element
56574              * @method afterValidDrop
56575              */
56576             this.afterValidDrop(target, e, id);
56577         }
56578     },
56579
56580     // private
56581     getRepairXY: function(e, data){
56582         return this.el.getXY();
56583     },
56584
56585     // private
56586     onInvalidDrop: function(target, e, id) {
56587         this.beforeInvalidDrop(target, e, id);
56588         if (this.cachedTarget) {
56589             if(this.cachedTarget.isNotifyTarget){
56590                 this.cachedTarget.notifyOut(this, e, this.dragData);
56591             }
56592             this.cacheTarget = null;
56593         }
56594         this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
56595
56596         if (this.afterInvalidDrop) {
56597             /**
56598              * An empty function by default, but provided so that you can perform a custom action
56599              * after an invalid drop has occurred by providing an implementation.
56600              * @param {Event} e The event object
56601              * @param {String} id The id of the dropped element
56602              * @method afterInvalidDrop
56603              */
56604             this.afterInvalidDrop(e, id);
56605         }
56606     },
56607
56608     // private
56609     afterRepair: function() {
56610         var me = this;
56611         if (Ext.enableFx) {
56612             me.el.highlight(me.repairHighlightColor);
56613         }
56614         me.dragging = false;
56615     },
56616
56617     /**
56618      * An empty function by default, but provided so that you can perform a custom action after an invalid
56619      * drop has occurred.
56620      * @param {Ext.dd.DragDrop} target The drop target
56621      * @param {Event} e The event object
56622      * @param {String} id The id of the dragged element
56623      * @return {Boolean} isValid True if the invalid drop should proceed, else false to cancel
56624      */
56625     beforeInvalidDrop: function(target, e, id) {
56626         return true;
56627     },
56628
56629     // private
56630     handleMouseDown: function(e) {
56631         if (this.dragging) {
56632             return;
56633         }
56634         var data = this.getDragData(e);
56635         if (data && this.onBeforeDrag(data, e) !== false) {
56636             this.dragData = data;
56637             this.proxy.stop();
56638             this.callParent(arguments);
56639         }
56640     },
56641
56642     /**
56643      * An empty function by default, but provided so that you can perform a custom action before the initial
56644      * drag event begins and optionally cancel it.
56645      * @param {Object} data An object containing arbitrary data to be shared with drop targets
56646      * @param {Event} e The event object
56647      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
56648      */
56649     onBeforeDrag: function(data, e){
56650         return true;
56651     },
56652
56653     /**
56654      * An empty function by default, but provided so that you can perform a custom action once the initial
56655      * drag event has begun.  The drag cannot be canceled from this function.
56656      * @param {Number} x The x position of the click on the dragged object
56657      * @param {Number} y The y position of the click on the dragged object
56658      * @method
56659      */
56660     onStartDrag: Ext.emptyFn,
56661
56662     // private override
56663     startDrag: function(x, y) {
56664         this.proxy.reset();
56665         this.dragging = true;
56666         this.proxy.update("");
56667         this.onInitDrag(x, y);
56668         this.proxy.show();
56669     },
56670
56671     // private
56672     onInitDrag: function(x, y) {
56673         var clone = this.el.dom.cloneNode(true);
56674         clone.id = Ext.id(); // prevent duplicate ids
56675         this.proxy.update(clone);
56676         this.onStartDrag(x, y);
56677         return true;
56678     },
56679
56680     /**
56681      * Returns the drag source's underlying {@link Ext.dd.StatusProxy}
56682      * @return {Ext.dd.StatusProxy} proxy The StatusProxy
56683      */
56684     getProxy: function() {
56685         return this.proxy;
56686     },
56687
56688     /**
56689      * Hides the drag source's {@link Ext.dd.StatusProxy}
56690      */
56691     hideProxy: function() {
56692         this.proxy.hide();
56693         this.proxy.reset(true);
56694         this.dragging = false;
56695     },
56696
56697     // private
56698     triggerCacheRefresh: function() {
56699         Ext.dd.DDM.refreshCache(this.groups);
56700     },
56701
56702     // private - override to prevent hiding
56703     b4EndDrag: function(e) {
56704     },
56705
56706     // private - override to prevent moving
56707     endDrag : function(e){
56708         this.onEndDrag(this.dragData, e);
56709     },
56710
56711     // private
56712     onEndDrag : function(data, e){
56713     },
56714
56715     // private - pin to cursor
56716     autoOffset : function(x, y) {
56717         this.setDelta(-12, -20);
56718     },
56719
56720     destroy: function(){
56721         this.callParent();
56722         Ext.destroy(this.proxy);
56723     }
56724 });
56725
56726 // private - DD implementation for Panels
56727 Ext.define('Ext.panel.DD', {
56728     extend: 'Ext.dd.DragSource',
56729     requires: ['Ext.panel.Proxy'],
56730
56731     constructor : function(panel, cfg){
56732         this.panel = panel;
56733         this.dragData = {panel: panel};
56734         this.proxy = Ext.create('Ext.panel.Proxy', panel, cfg);
56735
56736         this.callParent([panel.el, cfg]);
56737
56738         Ext.defer(function() {
56739             var header = panel.header,
56740                 el = panel.body;
56741
56742             if(header){
56743                 this.setHandleElId(header.id);
56744                 el = header.el;
56745             }
56746             el.setStyle('cursor', 'move');
56747             this.scroll = false;
56748         }, 200, this);
56749     },
56750
56751     showFrame: Ext.emptyFn,
56752     startDrag: Ext.emptyFn,
56753     b4StartDrag: function(x, y) {
56754         this.proxy.show();
56755     },
56756     b4MouseDown: function(e) {
56757         var x = e.getPageX(),
56758             y = e.getPageY();
56759         this.autoOffset(x, y);
56760     },
56761     onInitDrag : function(x, y){
56762         this.onStartDrag(x, y);
56763         return true;
56764     },
56765     createFrame : Ext.emptyFn,
56766     getDragEl : function(e){
56767         return this.proxy.ghost.el.dom;
56768     },
56769     endDrag : function(e){
56770         this.proxy.hide();
56771         this.panel.saveState();
56772     },
56773
56774     autoOffset : function(x, y) {
56775         x -= this.startPageX;
56776         y -= this.startPageY;
56777         this.setDelta(x, y);
56778     }
56779 });
56780
56781 /**
56782  * @class Ext.layout.component.Dock
56783  * @extends Ext.layout.component.AbstractDock
56784  * @private
56785  */
56786 Ext.define('Ext.layout.component.Dock', {
56787
56788     /* Begin Definitions */
56789
56790     alias: ['layout.dock'],
56791
56792     extend: 'Ext.layout.component.AbstractDock'
56793
56794     /* End Definitions */
56795
56796 });
56797 /**
56798  * Panel is a container that has specific functionality and structural components that make it the perfect building
56799  * block for application-oriented user interfaces.
56800  *
56801  * Panels are, by virtue of their inheritance from {@link Ext.container.Container}, capable of being configured with a
56802  * {@link Ext.container.Container#layout layout}, and containing child Components.
56803  *
56804  * When either specifying child {@link #items} of a Panel, or dynamically {@link Ext.container.Container#add adding}
56805  * Components to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether those
56806  * child elements need to be sized using one of Ext's built-in `{@link Ext.container.Container#layout layout}`
56807  * schemes. By default, Panels use the {@link Ext.layout.container.Auto Auto} scheme. This simply renders child
56808  * components, appending them one after the other inside the Container, and **does not apply any sizing** at all.
56809  *
56810  * {@img Ext.panel.Panel/panel.png Panel components}
56811  *
56812  * A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate {@link
56813  * Ext.panel.Header header}, {@link #fbar footer} and body sections.
56814  *
56815  * Panel also provides built-in {@link #collapsible collapsible, expandable} and {@link #closable} behavior. Panels can
56816  * be easily dropped into any {@link Ext.container.Container Container} or layout, and the layout and rendering pipeline
56817  * is {@link Ext.container.Container#add completely managed by the framework}.
56818  *
56819  * **Note:** By default, the `{@link #closable close}` header tool _destroys_ the Panel resulting in removal of the
56820  * Panel and the destruction of any descendant Components. This makes the Panel object, and all its descendants
56821  * **unusable**. To enable the close tool to simply _hide_ a Panel for later re-use, configure the Panel with
56822  * `{@link #closeAction closeAction}: 'hide'`.
56823  *
56824  * Usually, Panels are used as constituents within an application, in which case, they would be used as child items of
56825  * Containers, and would themselves use Ext.Components as child {@link #items}. However to illustrate simply rendering a
56826  * Panel into the document, here's how to do it:
56827  *
56828  *     @example
56829  *     Ext.create('Ext.panel.Panel', {
56830  *         title: 'Hello',
56831  *         width: 200,
56832  *         html: '<p>World!</p>',
56833  *         renderTo: Ext.getBody()
56834  *     });
56835  *
56836  * A more realistic scenario is a Panel created to house input fields which will not be rendered, but used as a
56837  * constituent part of a Container:
56838  *
56839  *     @example
56840  *     var filterPanel = Ext.create('Ext.panel.Panel', {
56841  *         bodyPadding: 5,  // Don't want content to crunch against the borders
56842  *         width: 300,
56843  *         title: 'Filters',
56844  *         items: [{
56845  *             xtype: 'datefield',
56846  *             fieldLabel: 'Start date'
56847  *         }, {
56848  *             xtype: 'datefield',
56849  *             fieldLabel: 'End date'
56850  *         }],
56851  *         renderTo: Ext.getBody()
56852  *     });
56853  *
56854  * Note that the Panel above is not configured to render into the document, nor is it configured with a size or
56855  * position. In a real world scenario, the Container into which the Panel is added will use a {@link #layout} to render,
56856  * size and position its child Components.
56857  *
56858  * Panels will often use specific {@link #layout}s to provide an application with shape and structure by containing and
56859  * arranging child Components:
56860  *
56861  *     @example
56862  *     var resultsPanel = Ext.create('Ext.panel.Panel', {
56863  *         title: 'Results',
56864  *         width: 600,
56865  *         height: 400,
56866  *         renderTo: Ext.getBody(),
56867  *         layout: {
56868  *             type: 'vbox',       // Arrange child items vertically
56869  *             align: 'stretch',    // Each takes up full width
56870  *             padding: 5
56871  *         },
56872  *         items: [{               // Results grid specified as a config object with an xtype of 'grid'
56873  *             xtype: 'grid',
56874  *             columns: [{header: 'Column One'}],            // One header just for show. There's no data,
56875  *             store: Ext.create('Ext.data.ArrayStore', {}), // A dummy empty data store
56876  *             flex: 1                                       // Use 1/3 of Container's height (hint to Box layout)
56877  *         }, {
56878  *             xtype: 'splitter'   // A splitter between the two child items
56879  *         }, {                    // Details Panel specified as a config object (no xtype defaults to 'panel').
56880  *             title: 'Details',
56881  *             bodyPadding: 5,
56882  *             items: [{
56883  *                 fieldLabel: 'Data item',
56884  *                 xtype: 'textfield'
56885  *             }], // An array of form fields
56886  *             flex: 2             // Use 2/3 of Container's height (hint to Box layout)
56887  *         }]
56888  *     });
56889  *
56890  * The example illustrates one possible method of displaying search results. The Panel contains a grid with the
56891  * resulting data arranged in rows. Each selected row may be displayed in detail in the Panel below. The {@link
56892  * Ext.layout.container.VBox vbox} layout is used to arrange the two vertically. It is configured to stretch child items
56893  * horizontally to full width. Child items may either be configured with a numeric height, or with a `flex` value to
56894  * distribute available space proportionately.
56895  *
56896  * This Panel itself may be a child item of, for exaple, a {@link Ext.tab.Panel} which will size its child items to fit
56897  * within its content area.
56898  *
56899  * Using these techniques, as long as the **layout** is chosen and configured correctly, an application may have any
56900  * level of nested containment, all dynamically sized according to configuration, the user's preference and available
56901  * browser size.
56902  */
56903 Ext.define('Ext.panel.Panel', {
56904     extend: 'Ext.panel.AbstractPanel',
56905     requires: [
56906         'Ext.panel.Header',
56907         'Ext.fx.Anim',
56908         'Ext.util.KeyMap',
56909         'Ext.panel.DD',
56910         'Ext.XTemplate',
56911         'Ext.layout.component.Dock',
56912         'Ext.util.Memento'
56913     ],
56914     alias: 'widget.panel',
56915     alternateClassName: 'Ext.Panel',
56916
56917     /**
56918      * @cfg {String} collapsedCls
56919      * A CSS class to add to the panel's element after it has been collapsed.
56920      */
56921     collapsedCls: 'collapsed',
56922
56923     /**
56924      * @cfg {Boolean} animCollapse
56925      * `true` to animate the transition when the panel is collapsed, `false` to skip the animation (defaults to `true`
56926      * if the {@link Ext.fx.Anim} class is available, otherwise `false`). May also be specified as the animation
56927      * duration in milliseconds.
56928      */
56929     animCollapse: Ext.enableFx,
56930
56931     /**
56932      * @cfg {Number} minButtonWidth
56933      * Minimum width of all footer toolbar buttons in pixels. If set, this will be used as the default
56934      * value for the {@link Ext.button.Button#minWidth} config of each Button added to the **footer toolbar** via the
56935      * {@link #fbar} or {@link #buttons} configurations. It will be ignored for buttons that have a minWidth configured
56936      * some other way, e.g. in their own config object or via the {@link Ext.container.Container#defaults defaults} of
56937      * their parent container.
56938      */
56939     minButtonWidth: 75,
56940
56941     /**
56942      * @cfg {Boolean} collapsed
56943      * `true` to render the panel collapsed, `false` to render it expanded.
56944      */
56945     collapsed: false,
56946
56947     /**
56948      * @cfg {Boolean} collapseFirst
56949      * `true` to make sure the collapse/expand toggle button always renders first (to the left of) any other tools in
56950      * the panel's title bar, `false` to render it last.
56951      */
56952     collapseFirst: true,
56953
56954     /**
56955      * @cfg {Boolean} hideCollapseTool
56956      * `true` to hide the expand/collapse toggle button when `{@link #collapsible} == true`, `false` to display it.
56957      */
56958     hideCollapseTool: false,
56959
56960     /**
56961      * @cfg {Boolean} titleCollapse
56962      * `true` to allow expanding and collapsing the panel (when `{@link #collapsible} = true`) by clicking anywhere in
56963      * the header bar, `false`) to allow it only by clicking to tool butto).
56964      */
56965     titleCollapse: false,
56966
56967     /**
56968      * @cfg {String} collapseMode
56969      * **Important: this config is only effective for {@link #collapsible} Panels which are direct child items of a
56970      * {@link Ext.layout.container.Border border layout}.**
56971      *
56972      * When _not_ a direct child item of a {@link Ext.layout.container.Border border layout}, then the Panel's header
56973      * remains visible, and the body is collapsed to zero dimensions. If the Panel has no header, then a new header
56974      * (orientated correctly depending on the {@link #collapseDirection}) will be inserted to show a the title and a re-
56975      * expand tool.
56976      *
56977      * When a child item of a {@link Ext.layout.container.Border border layout}, this config has two options:
56978      *
56979      * - **`undefined/omitted`**
56980      *
56981      *   When collapsed, a placeholder {@link Ext.panel.Header Header} is injected into the layout to represent the Panel
56982      *   and to provide a UI with a Tool to allow the user to re-expand the Panel.
56983      *
56984      * - **`header`** :
56985      *
56986      *   The Panel collapses to leave its header visible as when not inside a {@link Ext.layout.container.Border border
56987      *   layout}.
56988      */
56989
56990     /**
56991      * @cfg {Ext.Component/Object} placeholder
56992      * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
56993      * {@link Ext.layout.container.Border border layout} when not using the `'header'` {@link #collapseMode}.**
56994      *
56995      * **Optional.** A Component (or config object for a Component) to show in place of this Panel when this Panel is
56996      * collapsed by a {@link Ext.layout.container.Border border layout}. Defaults to a generated {@link Ext.panel.Header
56997      * Header} containing a {@link Ext.panel.Tool Tool} to re-expand the Panel.
56998      */
56999
57000     /**
57001      * @cfg {Boolean} floatable
57002      * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
57003      * {@link Ext.layout.container.Border border layout}.**
57004      *
57005      * true to allow clicking a collapsed Panel's {@link #placeholder} to display the Panel floated above the layout,
57006      * false to force the user to fully expand a collapsed region by clicking the expand button to see it again.
57007      */
57008     floatable: true,
57009
57010     /**
57011      * @cfg {Boolean} overlapHeader
57012      * True to overlap the header in a panel over the framing of the panel itself. This is needed when frame:true (and
57013      * is done automatically for you). Otherwise it is undefined. If you manually add rounded corners to a panel header
57014      * which does not have frame:true, this will need to be set to true.
57015      */
57016
57017     /**
57018      * @cfg {Boolean} collapsible
57019      * True to make the panel collapsible and have an expand/collapse toggle Tool added into the header tool button
57020      * area. False to keep the panel sized either statically, or by an owning layout manager, with no toggle Tool.
57021      *
57022      * See {@link #collapseMode} and {@link #collapseDirection}
57023      */
57024     collapsible: false,
57025
57026     /**
57027      * @cfg {Boolean} collapseDirection
57028      * The direction to collapse the Panel when the toggle button is clicked.
57029      *
57030      * Defaults to the {@link #headerPosition}
57031      *
57032      * **Important: This config is _ignored_ for {@link #collapsible} Panels which are direct child items of a {@link
57033      * Ext.layout.container.Border border layout}.**
57034      *
57035      * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
57036      */
57037
57038     /**
57039      * @cfg {Boolean} closable
57040      * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
57041      * disallow closing the window.
57042      *
57043      * By default, when close is requested by clicking the close button in the header, the {@link #close} method will be
57044      * called. This will _{@link Ext.Component#destroy destroy}_ the Panel and its content meaning that it may not be
57045      * reused.
57046      *
57047      * To make closing a Panel _hide_ the Panel so that it may be reused, set {@link #closeAction} to 'hide'.
57048      */
57049     closable: false,
57050
57051     /**
57052      * @cfg {String} closeAction
57053      * The action to take when the close header tool is clicked:
57054      *
57055      * - **`'{@link #destroy}'`** :
57056      *
57057      *   {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy} it and all descendant
57058      *   Components. The window will **not** be available to be redisplayed via the {@link #show} method.
57059      *
57060      * - **`'{@link #hide}'`** :
57061      *
57062      *   {@link #hide} the window by setting visibility to hidden and applying negative offsets. The window will be
57063      *   available to be redisplayed via the {@link #show} method.
57064      *
57065      * **Note:** This behavior has changed! setting *does* affect the {@link #close} method which will invoke the
57066      * approriate closeAction.
57067      */
57068     closeAction: 'destroy',
57069
57070     /**
57071      * @cfg {Object/Object[]} dockedItems
57072      * A component or series of components to be added as docked items to this panel. The docked items can be docked to
57073      * either the top, right, left or bottom of a panel. This is typically used for things like toolbars or tab bars:
57074      *
57075      *     var panel = new Ext.panel.Panel({
57076      *         dockedItems: [{
57077      *             xtype: 'toolbar',
57078      *             dock: 'top',
57079      *             items: [{
57080      *                 text: 'Docked to the top'
57081      *             }]
57082      *         }]
57083      *     });
57084      */
57085
57086     /**
57087       * @cfg {Boolean} preventHeader
57088       * Prevent a Header from being created and shown.
57089       */
57090     preventHeader: false,
57091
57092      /**
57093       * @cfg {String} headerPosition
57094       * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
57095       */
57096     headerPosition: 'top',
57097
57098      /**
57099      * @cfg {Boolean} frame
57100      * True to apply a frame to the panel.
57101      */
57102     frame: false,
57103
57104     /**
57105      * @cfg {Boolean} frameHeader
57106      * True to apply a frame to the panel panels header (if 'frame' is true).
57107      */
57108     frameHeader: true,
57109
57110     /**
57111      * @cfg {Object[]/Ext.panel.Tool[]} tools
57112      * An array of {@link Ext.panel.Tool} configs/instances to be added to the header tool area. The tools are stored as
57113      * child components of the header container. They can be accessed using {@link #down} and {#query}, as well as the
57114      * other component methods. The toggle tool is automatically created if {@link #collapsible} is set to true.
57115      *
57116      * Note that, apart from the toggle tool which is provided when a panel is collapsible, these tools only provide the
57117      * visual button. Any required functionality must be provided by adding handlers that implement the necessary
57118      * behavior.
57119      *
57120      * Example usage:
57121      *
57122      *     tools:[{
57123      *         type:'refresh',
57124      *         tooltip: 'Refresh form Data',
57125      *         // hidden:true,
57126      *         handler: function(event, toolEl, panel){
57127      *             // refresh logic
57128      *         }
57129      *     },
57130      *     {
57131      *         type:'help',
57132      *         tooltip: 'Get Help',
57133      *         handler: function(event, toolEl, panel){
57134      *             // show help here
57135      *         }
57136      *     }]
57137      */
57138
57139     /**
57140      * @cfg {String} [title='']
57141      * The title text to be used to display in the {@link Ext.panel.Header panel header}. When a
57142      * `title` is specified the {@link Ext.panel.Header} will automatically be created and displayed unless
57143      * {@link #preventHeader} is set to `true`.
57144      */
57145
57146     /**
57147      * @cfg {String} iconCls
57148      * CSS class for icon in header. Used for displaying an icon to the left of a title.
57149      */
57150
57151     initComponent: function() {
57152         var me = this,
57153             cls;
57154
57155         me.addEvents(
57156
57157             /**
57158              * @event beforeclose
57159              * Fires before the user closes the panel. Return false from any listener to stop the close event being
57160              * fired
57161              * @param {Ext.panel.Panel} panel The Panel object
57162              */
57163             'beforeclose',
57164
57165             /**
57166              * @event beforeexpand
57167              * Fires before this panel is expanded. Return false to prevent the expand.
57168              * @param {Ext.panel.Panel} p The Panel being expanded.
57169              * @param {Boolean} animate True if the expand is animated, else false.
57170              */
57171             "beforeexpand",
57172
57173             /**
57174              * @event beforecollapse
57175              * Fires before this panel is collapsed. Return false to prevent the collapse.
57176              * @param {Ext.panel.Panel} p The Panel being collapsed.
57177              * @param {String} direction . The direction of the collapse. One of
57178              *
57179              *   - Ext.Component.DIRECTION_TOP
57180              *   - Ext.Component.DIRECTION_RIGHT
57181              *   - Ext.Component.DIRECTION_BOTTOM
57182              *   - Ext.Component.DIRECTION_LEFT
57183              *
57184              * @param {Boolean} animate True if the collapse is animated, else false.
57185              */
57186             "beforecollapse",
57187
57188             /**
57189              * @event expand
57190              * Fires after this Panel has expanded.
57191              * @param {Ext.panel.Panel} p The Panel that has been expanded.
57192              */
57193             "expand",
57194
57195             /**
57196              * @event collapse
57197              * Fires after this Panel hass collapsed.
57198              * @param {Ext.panel.Panel} p The Panel that has been collapsed.
57199              */
57200             "collapse",
57201
57202             /**
57203              * @event titlechange
57204              * Fires after the Panel title has been set or changed.
57205              * @param {Ext.panel.Panel} p the Panel which has been resized.
57206              * @param {String} newTitle The new title.
57207              * @param {String} oldTitle The previous panel title.
57208              */
57209             'titlechange',
57210
57211             /**
57212              * @event iconchange
57213              * Fires after the Panel iconCls has been set or changed.
57214              * @param {Ext.panel.Panel} p the Panel which has been resized.
57215              * @param {String} newIconCls The new iconCls.
57216              * @param {String} oldIconCls The previous panel iconCls.
57217              */
57218             'iconchange'
57219         );
57220
57221         // Save state on these two events.
57222         this.addStateEvents('expand', 'collapse');
57223
57224         if (me.unstyled) {
57225             me.setUI('plain');
57226         }
57227
57228         if (me.frame) {
57229             me.setUI(me.ui + '-framed');
57230         }
57231
57232         // Backwards compatibility
57233         me.bridgeToolbars();
57234
57235         me.callParent();
57236         me.collapseDirection = me.collapseDirection || me.headerPosition || Ext.Component.DIRECTION_TOP;
57237     },
57238
57239     setBorder: function(border) {
57240         // var me     = this,
57241         //     method = (border === false || border === 0) ? 'addClsWithUI' : 'removeClsWithUI';
57242         //
57243         // me.callParent(arguments);
57244         //
57245         // if (me.collapsed) {
57246         //     me[method](me.collapsedCls + '-noborder');
57247         // }
57248         //
57249         // if (me.header) {
57250         //     me.header.setBorder(border);
57251         //     if (me.collapsed) {
57252         //         me.header[method](me.collapsedCls + '-noborder');
57253         //     }
57254         // }
57255
57256         this.callParent(arguments);
57257     },
57258
57259     beforeDestroy: function() {
57260         Ext.destroy(
57261             this.ghostPanel,
57262             this.dd
57263         );
57264         this.callParent();
57265     },
57266
57267     initAria: function() {
57268         this.callParent();
57269         this.initHeaderAria();
57270     },
57271
57272     initHeaderAria: function() {
57273         var me = this,
57274             el = me.el,
57275             header = me.header;
57276         if (el && header) {
57277             el.dom.setAttribute('aria-labelledby', header.titleCmp.id);
57278         }
57279     },
57280
57281     getHeader: function() {
57282         return this.header;
57283     },
57284
57285     /**
57286      * Set a title for the panel's header. See {@link Ext.panel.Header#title}.
57287      * @param {String} newTitle
57288      */
57289     setTitle: function(newTitle) {
57290         var me = this,
57291         oldTitle = this.title;
57292
57293         me.title = newTitle;
57294         if (me.header) {
57295             me.header.setTitle(newTitle);
57296         } else {
57297             me.updateHeader();
57298         }
57299
57300         if (me.reExpander) {
57301             me.reExpander.setTitle(newTitle);
57302         }
57303         me.fireEvent('titlechange', me, newTitle, oldTitle);
57304     },
57305
57306     /**
57307      * Set the iconCls for the panel's header. See {@link Ext.panel.Header#iconCls}. It will fire the
57308      * {@link #iconchange} event after completion.
57309      * @param {String} newIconCls The new CSS class name
57310      */
57311     setIconCls: function(newIconCls) {
57312         var me = this,
57313             oldIconCls = me.iconCls;
57314
57315         me.iconCls = newIconCls;
57316         var header = me.header;
57317         if (header) {
57318             header.setIconCls(newIconCls);
57319         }
57320         me.fireEvent('iconchange', me, newIconCls, oldIconCls);
57321     },
57322
57323     bridgeToolbars: function() {
57324         var me = this,
57325             docked = [],
57326             fbar,
57327             fbarDefaults,
57328             minButtonWidth = me.minButtonWidth;
57329
57330         function initToolbar (toolbar, pos, useButtonAlign) {
57331             if (Ext.isArray(toolbar)) {
57332                 toolbar = {
57333                     xtype: 'toolbar',
57334                     items: toolbar
57335                 };
57336             }
57337             else if (!toolbar.xtype) {
57338                 toolbar.xtype = 'toolbar';
57339             }
57340             toolbar.dock = pos;
57341             if (pos == 'left' || pos == 'right') {
57342                 toolbar.vertical = true;
57343             }
57344
57345             // Legacy support for buttonAlign (only used by buttons/fbar)
57346             if (useButtonAlign) {
57347                 toolbar.layout = Ext.applyIf(toolbar.layout || {}, {
57348                     // default to 'end' (right-aligned) if me.buttonAlign is undefined or invalid
57349                     pack: { left:'start', center:'center' }[me.buttonAlign] || 'end'
57350                 });
57351             }
57352             return toolbar;
57353         }
57354
57355         // Short-hand toolbars (tbar, bbar and fbar plus new lbar and rbar):
57356
57357         /**
57358          * @cfg {String} buttonAlign
57359          * The alignment of any buttons added to this panel. Valid values are 'right', 'left' and 'center' (defaults to
57360          * 'right' for buttons/fbar, 'left' for other toolbar types).
57361          *
57362          * **NOTE:** The prefered way to specify toolbars is to use the dockedItems config. Instead of buttonAlign you
57363          * would add the layout: { pack: 'start' | 'center' | 'end' } option to the dockedItem config.
57364          */
57365
57366         /**
57367          * @cfg {Object/Object[]} tbar
57368          * Convenience config. Short for 'Top Bar'.
57369          *
57370          *     tbar: [
57371          *       { xtype: 'button', text: 'Button 1' }
57372          *     ]
57373          *
57374          * is equivalent to
57375          *
57376          *     dockedItems: [{
57377          *         xtype: 'toolbar',
57378          *         dock: 'top',
57379          *         items: [
57380          *             { xtype: 'button', text: 'Button 1' }
57381          *         ]
57382          *     }]
57383          */
57384         if (me.tbar) {
57385             docked.push(initToolbar(me.tbar, 'top'));
57386             me.tbar = null;
57387         }
57388
57389         /**
57390          * @cfg {Object/Object[]} bbar
57391          * Convenience config. Short for 'Bottom Bar'.
57392          *
57393          *     bbar: [
57394          *       { xtype: 'button', text: 'Button 1' }
57395          *     ]
57396          *
57397          * is equivalent to
57398          *
57399          *     dockedItems: [{
57400          *         xtype: 'toolbar',
57401          *         dock: 'bottom',
57402          *         items: [
57403          *             { xtype: 'button', text: 'Button 1' }
57404          *         ]
57405          *     }]
57406          */
57407         if (me.bbar) {
57408             docked.push(initToolbar(me.bbar, 'bottom'));
57409             me.bbar = null;
57410         }
57411
57412         /**
57413          * @cfg {Object/Object[]} buttons
57414          * Convenience config used for adding buttons docked to the bottom of the panel. This is a
57415          * synonym for the {@link #fbar} config.
57416          *
57417          *     buttons: [
57418          *       { text: 'Button 1' }
57419          *     ]
57420          *
57421          * is equivalent to
57422          *
57423          *     dockedItems: [{
57424          *         xtype: 'toolbar',
57425          *         dock: 'bottom',
57426          *         ui: 'footer',
57427          *         defaults: {minWidth: {@link #minButtonWidth}},
57428          *         items: [
57429          *             { xtype: 'component', flex: 1 },
57430          *             { xtype: 'button', text: 'Button 1' }
57431          *         ]
57432          *     }]
57433          *
57434          * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
57435          * each of the buttons in the buttons toolbar.
57436          */
57437         if (me.buttons) {
57438             me.fbar = me.buttons;
57439             me.buttons = null;
57440         }
57441
57442         /**
57443          * @cfg {Object/Object[]} fbar
57444          * Convenience config used for adding items to the bottom of the panel. Short for Footer Bar.
57445          *
57446          *     fbar: [
57447          *       { type: 'button', text: 'Button 1' }
57448          *     ]
57449          *
57450          * is equivalent to
57451          *
57452          *     dockedItems: [{
57453          *         xtype: 'toolbar',
57454          *         dock: 'bottom',
57455          *         ui: 'footer',
57456          *         defaults: {minWidth: {@link #minButtonWidth}},
57457          *         items: [
57458          *             { xtype: 'component', flex: 1 },
57459          *             { xtype: 'button', text: 'Button 1' }
57460          *         ]
57461          *     }]
57462          *
57463          * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
57464          * each of the buttons in the fbar.
57465          */
57466         if (me.fbar) {
57467             fbar = initToolbar(me.fbar, 'bottom', true); // only we useButtonAlign
57468             fbar.ui = 'footer';
57469
57470             // Apply the minButtonWidth config to buttons in the toolbar
57471             if (minButtonWidth) {
57472                 fbarDefaults = fbar.defaults;
57473                 fbar.defaults = function(config) {
57474                     var defaults = fbarDefaults || {};
57475                     if ((!config.xtype || config.xtype === 'button' || (config.isComponent && config.isXType('button'))) &&
57476                             !('minWidth' in defaults)) {
57477                         defaults = Ext.apply({minWidth: minButtonWidth}, defaults);
57478                     }
57479                     return defaults;
57480                 };
57481             }
57482
57483             docked.push(fbar);
57484             me.fbar = null;
57485         }
57486
57487         /**
57488          * @cfg {Object/Object[]} lbar
57489          * Convenience config. Short for 'Left Bar' (left-docked, vertical toolbar).
57490          *
57491          *     lbar: [
57492          *       { xtype: 'button', text: 'Button 1' }
57493          *     ]
57494          *
57495          * is equivalent to
57496          *
57497          *     dockedItems: [{
57498          *         xtype: 'toolbar',
57499          *         dock: 'left',
57500          *         items: [
57501          *             { xtype: 'button', text: 'Button 1' }
57502          *         ]
57503          *     }]
57504          */
57505         if (me.lbar) {
57506             docked.push(initToolbar(me.lbar, 'left'));
57507             me.lbar = null;
57508         }
57509
57510         /**
57511          * @cfg {Object/Object[]} rbar
57512          * Convenience config. Short for 'Right Bar' (right-docked, vertical toolbar).
57513          *
57514          *     rbar: [
57515          *       { xtype: 'button', text: 'Button 1' }
57516          *     ]
57517          *
57518          * is equivalent to
57519          *
57520          *     dockedItems: [{
57521          *         xtype: 'toolbar',
57522          *         dock: 'right',
57523          *         items: [
57524          *             { xtype: 'button', text: 'Button 1' }
57525          *         ]
57526          *     }]
57527          */
57528         if (me.rbar) {
57529             docked.push(initToolbar(me.rbar, 'right'));
57530             me.rbar = null;
57531         }
57532
57533         if (me.dockedItems) {
57534             if (!Ext.isArray(me.dockedItems)) {
57535                 me.dockedItems = [me.dockedItems];
57536             }
57537             me.dockedItems = me.dockedItems.concat(docked);
57538         } else {
57539             me.dockedItems = docked;
57540         }
57541     },
57542
57543     /**
57544      * @private
57545      * Tools are a Panel-specific capabilty.
57546      * Panel uses initTools. Subclasses may contribute tools by implementing addTools.
57547      */
57548     initTools: function() {
57549         var me = this;
57550
57551         me.tools = me.tools ? Ext.Array.clone(me.tools) : [];
57552
57553         // Add a collapse tool unless configured to not show a collapse tool
57554         // or to not even show a header.
57555         if (me.collapsible && !(me.hideCollapseTool || me.header === false)) {
57556             me.collapseDirection = me.collapseDirection || me.headerPosition || 'top';
57557             me.collapseTool = me.expandTool = me.createComponent({
57558                 xtype: 'tool',
57559                 type: 'collapse-' + me.collapseDirection,
57560                 expandType: me.getOppositeDirection(me.collapseDirection),
57561                 handler: me.toggleCollapse,
57562                 scope: me
57563             });
57564
57565             // Prepend collapse tool is configured to do so.
57566             if (me.collapseFirst) {
57567                 me.tools.unshift(me.collapseTool);
57568             }
57569         }
57570
57571         // Add subclass-specific tools.
57572         me.addTools();
57573
57574         // Make Panel closable.
57575         if (me.closable) {
57576             me.addClsWithUI('closable');
57577             me.addTool({
57578                 type: 'close',
57579                 handler: Ext.Function.bind(me.close, this, [])
57580             });
57581         }
57582
57583         // Append collapse tool if needed.
57584         if (me.collapseTool && !me.collapseFirst) {
57585             me.tools.push(me.collapseTool);
57586         }
57587     },
57588
57589     /**
57590      * @private
57591      * @template
57592      * Template method to be implemented in subclasses to add their tools after the collapsible tool.
57593      */
57594     addTools: Ext.emptyFn,
57595
57596     /**
57597      * Closes the Panel. By default, this method, removes it from the DOM, {@link Ext.Component#destroy destroy}s the
57598      * Panel object and all its descendant Components. The {@link #beforeclose beforeclose} event is fired before the
57599      * close happens and will cancel the close action if it returns false.
57600      *
57601      * **Note:** This method is also affected by the {@link #closeAction} setting. For more explicit control use
57602      * {@link #destroy} and {@link #hide} methods.
57603      */
57604     close: function() {
57605         if (this.fireEvent('beforeclose', this) !== false) {
57606             this.doClose();
57607         }
57608     },
57609
57610     // private
57611     doClose: function() {
57612         this.fireEvent('close', this);
57613         this[this.closeAction]();
57614     },
57615
57616     onRender: function(ct, position) {
57617         var me = this,
57618             topContainer;
57619
57620         // Add class-specific header tools.
57621         // Panel adds collapsible and closable.
57622         me.initTools();
57623
57624         // Dock the header/title
57625         me.updateHeader();
57626
57627         // Call to super after adding the header, to prevent an unnecessary re-layout
57628         me.callParent(arguments);
57629     },
57630
57631     afterRender: function() {
57632         var me = this;
57633
57634         me.callParent(arguments);
57635
57636         // Instate the collapsed state after render. We need to wait for
57637         // this moment so that we have established at least some of our size (from our
57638         // configured dimensions or from content via the component layout)
57639         if (me.collapsed) {
57640             me.collapsed = false;
57641             me.collapse(null, false, true);
57642         }
57643     },
57644
57645     /**
57646      * Create, hide, or show the header component as appropriate based on the current config.
57647      * @private
57648      * @param {Boolean} force True to force the header to be created
57649      */
57650     updateHeader: function(force) {
57651         var me = this,
57652             header = me.header,
57653             title = me.title,
57654             tools = me.tools;
57655
57656         if (!me.preventHeader && (force || title || (tools && tools.length))) {
57657             if (!header) {
57658                 header = me.header = Ext.create('Ext.panel.Header', {
57659                     title       : title,
57660                     orientation : (me.headerPosition == 'left' || me.headerPosition == 'right') ? 'vertical' : 'horizontal',
57661                     dock        : me.headerPosition || 'top',
57662                     textCls     : me.headerTextCls,
57663                     iconCls     : me.iconCls,
57664                     baseCls     : me.baseCls + '-header',
57665                     tools       : tools,
57666                     ui          : me.ui,
57667                     indicateDrag: me.draggable,
57668                     border      : me.border,
57669                     frame       : me.frame && me.frameHeader,
57670                     ignoreParentFrame : me.frame || me.overlapHeader,
57671                     ignoreBorderManagement: me.frame || me.ignoreHeaderBorderManagement,
57672                     listeners   : me.collapsible && me.titleCollapse ? {
57673                         click: me.toggleCollapse,
57674                         scope: me
57675                     } : null
57676                 });
57677                 me.addDocked(header, 0);
57678
57679                 // Reference the Header's tool array.
57680                 // Header injects named references.
57681                 me.tools = header.tools;
57682             }
57683             header.show();
57684             me.initHeaderAria();
57685         } else if (header) {
57686             header.hide();
57687         }
57688     },
57689
57690     // inherit docs
57691     setUI: function(ui) {
57692         var me = this;
57693
57694         me.callParent(arguments);
57695
57696         if (me.header) {
57697             me.header.setUI(ui);
57698         }
57699     },
57700
57701     // private
57702     getContentTarget: function() {
57703         return this.body;
57704     },
57705
57706     getTargetEl: function() {
57707         return this.body || this.frameBody || this.el;
57708     },
57709
57710     // the overrides below allow for collapsed regions inside the border layout to be hidden
57711
57712     // inherit docs
57713     isVisible: function(deep){
57714         var me = this;
57715         if (me.collapsed && me.placeholder) {
57716             return me.placeholder.isVisible(deep);
57717         }
57718         return me.callParent(arguments);
57719     },
57720
57721     // inherit docs
57722     onHide: function(){
57723         var me = this;
57724         if (me.collapsed && me.placeholder) {
57725             me.placeholder.hide();
57726         } else {
57727             me.callParent(arguments);
57728         }
57729     },
57730
57731     // inherit docs
57732     onShow: function(){
57733         var me = this;
57734         if (me.collapsed && me.placeholder) {
57735             // force hidden back to true, since this gets set by the layout
57736             me.hidden = true;
57737             me.placeholder.show();
57738         } else {
57739             me.callParent(arguments);
57740         }
57741     },
57742
57743     addTool: function(tool) {
57744         var me = this,
57745             header = me.header;
57746
57747         if (Ext.isArray(tool)) {
57748             Ext.each(tool, me.addTool, me);
57749             return;
57750         }
57751         me.tools.push(tool);
57752         if (header) {
57753             header.addTool(tool);
57754         }
57755         me.updateHeader();
57756     },
57757
57758     getOppositeDirection: function(d) {
57759         var c = Ext.Component;
57760         switch (d) {
57761             case c.DIRECTION_TOP:
57762                 return c.DIRECTION_BOTTOM;
57763             case c.DIRECTION_RIGHT:
57764                 return c.DIRECTION_LEFT;
57765             case c.DIRECTION_BOTTOM:
57766                 return c.DIRECTION_TOP;
57767             case c.DIRECTION_LEFT:
57768                 return c.DIRECTION_RIGHT;
57769         }
57770     },
57771
57772     /**
57773      * Collapses the panel body so that the body becomes hidden. Docked Components parallel to the border towards which
57774      * the collapse takes place will remain visible. Fires the {@link #beforecollapse} event which will cancel the
57775      * collapse action if it returns false.
57776      *
57777      * @param {String} direction . The direction to collapse towards. Must be one of
57778      *
57779      *   - Ext.Component.DIRECTION_TOP
57780      *   - Ext.Component.DIRECTION_RIGHT
57781      *   - Ext.Component.DIRECTION_BOTTOM
57782      *   - Ext.Component.DIRECTION_LEFT
57783      *
57784      * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
57785      * {@link #animCollapse} panel config)
57786      * @return {Ext.panel.Panel} this
57787      */
57788     collapse: function(direction, animate, /* private - passed if called at render time */ internal) {
57789         var me = this,
57790             c = Ext.Component,
57791             height = me.getHeight(),
57792             width = me.getWidth(),
57793             frameInfo,
57794             newSize = 0,
57795             dockedItems = me.dockedItems.items,
57796             dockedItemCount = dockedItems.length,
57797             i = 0,
57798             comp,
57799             pos,
57800             anim = {
57801                 from: {
57802                     height: height,
57803                     width: width
57804                 },
57805                 to: {
57806                     height: height,
57807                     width: width
57808                 },
57809                 listeners: {
57810                     afteranimate: me.afterCollapse,
57811                     scope: me
57812                 },
57813                 duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration)
57814             },
57815             reExpander,
57816             reExpanderOrientation,
57817             reExpanderDock,
57818             getDimension,
57819             collapseDimension;
57820
57821         if (!direction) {
57822             direction = me.collapseDirection;
57823         }
57824
57825         // If internal (Called because of initial collapsed state), then no animation, and no events.
57826         if (internal) {
57827             animate = false;
57828         } else if (me.collapsed || me.fireEvent('beforecollapse', me, direction, animate) === false) {
57829             return false;
57830         }
57831
57832         reExpanderDock = direction;
57833         me.expandDirection = me.getOppositeDirection(direction);
57834
57835         // Track docked items which we hide during collapsed state
57836         me.hiddenDocked = [];
57837
57838         switch (direction) {
57839             case c.DIRECTION_TOP:
57840             case c.DIRECTION_BOTTOM:
57841                 reExpanderOrientation = 'horizontal';
57842                 collapseDimension = 'height';
57843                 getDimension = 'getHeight';
57844
57845                 // Attempt to find a reExpander Component (docked in a horizontal orientation)
57846                 // Also, collect all other docked items which we must hide after collapse.
57847                 for (; i < dockedItemCount; i++) {
57848                     comp = dockedItems[i];
57849                     if (comp.isVisible()) {
57850                         if (comp.isXType('header', true) && (!comp.dock || comp.dock == 'top' || comp.dock == 'bottom')) {
57851                             reExpander = comp;
57852                         } else {
57853                             me.hiddenDocked.push(comp);
57854                         }
57855                     } else if (comp === me.reExpander) {
57856                         reExpander = comp;
57857                     }
57858                 }
57859
57860                 if (direction == Ext.Component.DIRECTION_BOTTOM) {
57861                     pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
57862                     anim.from.top = pos;
57863                 }
57864                 break;
57865
57866             case c.DIRECTION_LEFT:
57867             case c.DIRECTION_RIGHT:
57868                 reExpanderOrientation = 'vertical';
57869                 collapseDimension = 'width';
57870                 getDimension = 'getWidth';
57871
57872                 // Attempt to find a reExpander Component (docked in a vecrtical orientation)
57873                 // Also, collect all other docked items which we must hide after collapse.
57874                 for (; i < dockedItemCount; i++) {
57875                     comp = dockedItems[i];
57876                     if (comp.isVisible()) {
57877                         if (comp.isHeader && (comp.dock == 'left' || comp.dock == 'right')) {
57878                             reExpander = comp;
57879                         } else {
57880                             me.hiddenDocked.push(comp);
57881                         }
57882                     } else if (comp === me.reExpander) {
57883                         reExpander = comp;
57884                     }
57885                 }
57886
57887                 if (direction == Ext.Component.DIRECTION_RIGHT) {
57888                     pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
57889                     anim.from.left = pos;
57890                 }
57891                 break;
57892
57893             default:
57894                 throw('Panel collapse must be passed a valid Component collapse direction');
57895         }
57896
57897         // Disable toggle tool during animated collapse
57898         if (animate && me.collapseTool) {
57899             me.collapseTool.disable();
57900         }
57901
57902         // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken.
57903         me.addClsWithUI(me.collapsedCls);
57904         // if (me.border === false) {
57905         //     me.addClsWithUI(me.collapsedCls + '-noborder');
57906         // }
57907
57908         // We found a header: Measure it to find the collapse-to size.
57909         if (reExpander && reExpander.rendered) {
57910
57911             //we must add the collapsed cls to the header and then remove to get the proper height
57912             reExpander.addClsWithUI(me.collapsedCls);
57913             reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57914             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57915                 reExpander.addClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
57916             }
57917
57918             frameInfo = reExpander.getFrameInfo();
57919
57920             //get the size
57921             newSize = reExpander[getDimension]() + (frameInfo ? frameInfo[direction] : 0);
57922
57923             //and remove
57924             reExpander.removeClsWithUI(me.collapsedCls);
57925             reExpander.removeClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57926             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57927                 reExpander.removeClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
57928             }
57929         }
57930         // No header: Render and insert a temporary one, and then measure it.
57931         else {
57932             reExpander = {
57933                 hideMode: 'offsets',
57934                 temporary: true,
57935                 title: me.title,
57936                 orientation: reExpanderOrientation,
57937                 dock: reExpanderDock,
57938                 textCls: me.headerTextCls,
57939                 iconCls: me.iconCls,
57940                 baseCls: me.baseCls + '-header',
57941                 ui: me.ui,
57942                 frame: me.frame && me.frameHeader,
57943                 ignoreParentFrame: me.frame || me.overlapHeader,
57944                 indicateDrag: me.draggable,
57945                 cls: me.baseCls + '-collapsed-placeholder ' + ' ' + Ext.baseCSSPrefix + 'docked ' + me.baseCls + '-' + me.ui + '-collapsed',
57946                 renderTo: me.el
57947             };
57948             if (!me.hideCollapseTool) {
57949                 reExpander[(reExpander.orientation == 'horizontal') ? 'tools' : 'items'] = [{
57950                     xtype: 'tool',
57951                     type: 'expand-' + me.expandDirection,
57952                     handler: me.toggleCollapse,
57953                     scope: me
57954                 }];
57955             }
57956
57957             // Capture the size of the re-expander.
57958             // For vertical headers in IE6 and IE7, this will be sized by a CSS rule in _panel.scss
57959             reExpander = me.reExpander = Ext.create('Ext.panel.Header', reExpander);
57960             newSize = reExpander[getDimension]() + ((reExpander.frame) ? reExpander.frameSize[direction] : 0);
57961             reExpander.hide();
57962
57963             // Insert the new docked item
57964             me.insertDocked(0, reExpander);
57965         }
57966
57967         me.reExpander = reExpander;
57968         me.reExpander.addClsWithUI(me.collapsedCls);
57969         me.reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57970         if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57971             me.reExpander.addClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
57972         }
57973
57974         // If collapsing right or down, we'll be also animating the left or top.
57975         if (direction == Ext.Component.DIRECTION_RIGHT) {
57976             anim.to.left = pos + (width - newSize);
57977         } else if (direction == Ext.Component.DIRECTION_BOTTOM) {
57978             anim.to.top = pos + (height - newSize);
57979         }
57980
57981         // Animate to the new size
57982         anim.to[collapseDimension] = newSize;
57983
57984         // When we collapse a panel, the panel is in control of one dimension (depending on
57985         // collapse direction) and sets that on the component. We must restore the user's
57986         // original value (including non-existance) when we expand. Using this technique, we
57987         // mimic setCalculatedSize for the dimension we do not control and setSize for the
57988         // one we do (only while collapsed).
57989         if (!me.collapseMemento) {
57990             me.collapseMemento = new Ext.util.Memento(me);
57991         }
57992         me.collapseMemento.capture(['width', 'height', 'minWidth', 'minHeight', 'layoutManagedHeight', 'layoutManagedWidth']);
57993
57994         // Remove any flex config before we attempt to collapse.
57995         me.savedFlex = me.flex;
57996         me.minWidth = 0;
57997         me.minHeight = 0;
57998         delete me.flex;
57999         me.suspendLayout = true;
58000
58001         if (animate) {
58002             me.animate(anim);
58003         } else {
58004             me.setSize(anim.to.width, anim.to.height);
58005             if (Ext.isDefined(anim.to.left) || Ext.isDefined(anim.to.top)) {
58006                 me.setPosition(anim.to.left, anim.to.top);
58007             }
58008             me.afterCollapse(false, internal);
58009         }
58010         return me;
58011     },
58012
58013     afterCollapse: function(animated, internal) {
58014         var me = this,
58015             i = 0,
58016             l = me.hiddenDocked.length;
58017
58018         me.collapseMemento.restore(['minWidth', 'minHeight']);
58019
58020         // Now we can restore the dimension we don't control to its original state
58021         // Leave the value in the memento so that it can be correctly restored
58022         // if it is set by animation.
58023         if (Ext.Component.VERTICAL_DIRECTION_Re.test(me.expandDirection)) {
58024             me.layoutManagedHeight = 2;
58025             me.collapseMemento.restore('width', false);
58026         } else {
58027             me.layoutManagedWidth = 2;
58028             me.collapseMemento.restore('height', false);
58029         }
58030
58031         // We must hide the body, otherwise it overlays docked items which come before
58032         // it in the DOM order. Collapsing its dimension won't work - padding and borders keep a size.
58033         me.saveScrollTop = me.body.dom.scrollTop;
58034         me.body.setStyle('display', 'none');
58035
58036         for (; i < l; i++) {
58037             me.hiddenDocked[i].hide();
58038         }
58039         if (me.reExpander) {
58040             me.reExpander.updateFrame();
58041             me.reExpander.show();
58042         }
58043         me.collapsed = true;
58044         me.suspendLayout = false;
58045
58046         if (!internal) {
58047             if (me.ownerCt) {
58048                 // Because Component layouts only inform upstream containers if they have changed size,
58049                 // explicitly lay out the container now, because the lastComponentsize will have been set by the non-animated setCalculatedSize.
58050                 if (animated) {
58051                     me.ownerCt.layout.layout();
58052                 }
58053             } else if (me.reExpander.temporary) {
58054                 me.doComponentLayout();
58055             }
58056         }
58057
58058         if (me.resizer) {
58059             me.resizer.disable();
58060         }
58061
58062         // If me Panel was configured with a collapse tool in its header, flip it's type
58063         if (me.collapseTool) {
58064             me.collapseTool.setType('expand-' + me.expandDirection);
58065         }
58066         if (!internal) {
58067             me.fireEvent('collapse', me);
58068         }
58069
58070         // Re-enable the toggle tool after an animated collapse
58071         if (animated && me.collapseTool) {
58072             me.collapseTool.enable();
58073         }
58074     },
58075
58076     /**
58077      * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will cancel the
58078      * expand action if it returns false.
58079      * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
58080      * {@link #animCollapse} panel config)
58081      * @return {Ext.panel.Panel} this
58082      */
58083     expand: function(animate) {
58084         var me = this;
58085         if (!me.collapsed || me.fireEvent('beforeexpand', me, animate) === false) {
58086             return false;
58087         }
58088
58089         var i = 0,
58090             l = me.hiddenDocked.length,
58091             direction = me.expandDirection,
58092             height = me.getHeight(),
58093             width = me.getWidth(),
58094             pos, anim;
58095
58096         // Disable toggle tool during animated expand
58097         if (animate && me.collapseTool) {
58098             me.collapseTool.disable();
58099         }
58100
58101         // Show any docked items that we hid on collapse
58102         // And hide the injected reExpander Header
58103         for (; i < l; i++) {
58104             me.hiddenDocked[i].hidden = false;
58105             me.hiddenDocked[i].el.show();
58106         }
58107         if (me.reExpander) {
58108             if (me.reExpander.temporary) {
58109                 me.reExpander.hide();
58110             } else {
58111                 me.reExpander.removeClsWithUI(me.collapsedCls);
58112                 me.reExpander.removeClsWithUI(me.collapsedCls + '-' + me.reExpander.dock);
58113                 if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
58114                     me.reExpander.removeClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
58115                 }
58116                 me.reExpander.updateFrame();
58117             }
58118         }
58119
58120         // If me Panel was configured with a collapse tool in its header, flip it's type
58121         if (me.collapseTool) {
58122             me.collapseTool.setType('collapse-' + me.collapseDirection);
58123         }
58124
58125         // Restore body display and scroll position
58126         me.body.setStyle('display', '');
58127         me.body.dom.scrollTop = me.saveScrollTop;
58128
58129         // Unset the flag before the potential call to calculateChildBox to calculate our newly flexed size
58130         me.collapsed = false;
58131
58132         // Remove any collapsed styling before any animation begins
58133         me.removeClsWithUI(me.collapsedCls);
58134         // if (me.border === false) {
58135         //     me.removeClsWithUI(me.collapsedCls + '-noborder');
58136         // }
58137
58138         anim = {
58139             to: {
58140             },
58141             from: {
58142                 height: height,
58143                 width: width
58144             },
58145             listeners: {
58146                 afteranimate: me.afterExpand,
58147                 scope: me
58148             }
58149         };
58150
58151         if ((direction == Ext.Component.DIRECTION_TOP) || (direction == Ext.Component.DIRECTION_BOTTOM)) {
58152
58153             // Restore the collapsed dimension.
58154             // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
58155             me.collapseMemento.restore('height', false);
58156
58157             // If autoHeight, measure the height now we have shown the body element.
58158             if (me.height === undefined) {
58159                 me.setCalculatedSize(me.width, null);
58160                 anim.to.height = me.getHeight();
58161
58162                 // Must size back down to collapsed for the animation.
58163                 me.setCalculatedSize(me.width, anim.from.height);
58164             }
58165             // If we were flexed, then we can't just restore to the saved size.
58166             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
58167             else if (me.savedFlex) {
58168                 me.flex = me.savedFlex;
58169                 anim.to.height = me.ownerCt.layout.calculateChildBox(me).height;
58170                 delete me.flex;
58171             }
58172             // Else, restore to saved height
58173             else {
58174                 anim.to.height = me.height;
58175             }
58176
58177             // top needs animating upwards
58178             if (direction == Ext.Component.DIRECTION_TOP) {
58179                 pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
58180                 anim.from.top = pos;
58181                 anim.to.top = pos - (anim.to.height - height);
58182             }
58183         } else if ((direction == Ext.Component.DIRECTION_LEFT) || (direction == Ext.Component.DIRECTION_RIGHT)) {
58184
58185             // Restore the collapsed dimension.
58186             // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
58187             me.collapseMemento.restore('width', false);
58188
58189             // If autoWidth, measure the width now we have shown the body element.
58190             if (me.width === undefined) {
58191                 me.setCalculatedSize(null, me.height);
58192                 anim.to.width = me.getWidth();
58193
58194                 // Must size back down to collapsed for the animation.
58195                 me.setCalculatedSize(anim.from.width, me.height);
58196             }
58197             // If we were flexed, then we can't just restore to the saved size.
58198             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
58199             else if (me.savedFlex) {
58200                 me.flex = me.savedFlex;
58201                 anim.to.width = me.ownerCt.layout.calculateChildBox(me).width;
58202                 delete me.flex;
58203             }
58204             // Else, restore to saved width
58205             else {
58206                 anim.to.width = me.width;
58207             }
58208
58209             // left needs animating leftwards
58210             if (direction == Ext.Component.DIRECTION_LEFT) {
58211                 pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
58212                 anim.from.left = pos;
58213                 anim.to.left = pos - (anim.to.width - width);
58214             }
58215         }
58216
58217         if (animate) {
58218             me.animate(anim);
58219         } else {
58220             me.setCalculatedSize(anim.to.width, anim.to.height);
58221             if (anim.to.x) {
58222                 me.setLeft(anim.to.x);
58223             }
58224             if (anim.to.y) {
58225                 me.setTop(anim.to.y);
58226             }
58227             me.afterExpand(false);
58228         }
58229
58230         return me;
58231     },
58232
58233     afterExpand: function(animated) {
58234         var me = this;
58235
58236         // Restored to a calculated flex. Delete the set width and height properties so that flex works from now on.
58237         if (me.savedFlex) {
58238             me.flex = me.savedFlex;
58239             delete me.savedFlex;
58240             delete me.width;
58241             delete me.height;
58242         }
58243
58244         // Restore width/height and dimension management flags to original values
58245         if (me.collapseMemento) {
58246             me.collapseMemento.restoreAll();
58247         }
58248
58249         if (animated && me.ownerCt) {
58250             // IE 6 has an intermittent repaint issue in this case so give
58251             // it a little extra time to catch up before laying out.
58252             Ext.defer(me.ownerCt.doLayout, Ext.isIE6 ? 1 : 0, me);
58253         }
58254
58255         if (me.resizer) {
58256             me.resizer.enable();
58257         }
58258
58259         me.fireEvent('expand', me);
58260
58261         // Re-enable the toggle tool after an animated expand
58262         if (animated && me.collapseTool) {
58263             me.collapseTool.enable();
58264         }
58265     },
58266
58267     /**
58268      * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
58269      * @return {Ext.panel.Panel} this
58270      */
58271     toggleCollapse: function() {
58272         if (this.collapsed) {
58273             this.expand(this.animCollapse);
58274         } else {
58275             this.collapse(this.collapseDirection, this.animCollapse);
58276         }
58277         return this;
58278     },
58279
58280     // private
58281     getKeyMap : function(){
58282         if(!this.keyMap){
58283             this.keyMap = Ext.create('Ext.util.KeyMap', this.el, this.keys);
58284         }
58285         return this.keyMap;
58286     },
58287
58288     // private
58289     initDraggable : function(){
58290         /**
58291          * @property {Ext.dd.DragSource} dd
58292          * If this Panel is configured {@link #draggable}, this property will contain an instance of {@link
58293          * Ext.dd.DragSource} which handles dragging the Panel.
58294          *
58295          * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource} in order to
58296          * supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
58297          */
58298         this.dd = Ext.create('Ext.panel.DD', this, Ext.isBoolean(this.draggable) ? null : this.draggable);
58299     },
58300
58301     // private - helper function for ghost
58302     ghostTools : function() {
58303         var tools = [],
58304             headerTools = this.header.query('tool[hidden=false]');
58305
58306         if (headerTools.length) {
58307             Ext.each(headerTools, function(tool) {
58308                 // Some tools can be full components, and copying them into the ghost
58309                 // actually removes them from the owning panel. You could also potentially
58310                 // end up with duplicate DOM ids as well. To avoid any issues we just make
58311                 // a simple bare-minimum clone of each tool for ghosting purposes.
58312                 tools.push({
58313                     type: tool.type
58314                 });
58315             });
58316         } else {
58317             tools = [{
58318                 type: 'placeholder'
58319             }];
58320         }
58321         return tools;
58322     },
58323
58324     // private - used for dragging
58325     ghost: function(cls) {
58326         var me = this,
58327             ghostPanel = me.ghostPanel,
58328             box = me.getBox(),
58329             header;
58330
58331         if (!ghostPanel) {
58332             ghostPanel = Ext.create('Ext.panel.Panel', {
58333                 renderTo: me.floating ? me.el.dom.parentNode : document.body,
58334                 floating: {
58335                     shadow: false
58336                 },
58337                 frame: Ext.supports.CSS3BorderRadius ? me.frame : false,
58338                 overlapHeader: me.overlapHeader,
58339                 headerPosition: me.headerPosition,
58340                 baseCls: me.baseCls,
58341                 cls: me.baseCls + '-ghost ' + (cls ||'')
58342             });
58343             me.ghostPanel = ghostPanel;
58344         }
58345         ghostPanel.floatParent = me.floatParent;
58346         if (me.floating) {
58347             ghostPanel.setZIndex(Ext.Number.from(me.el.getStyle('zIndex'), 0));
58348         } else {
58349             ghostPanel.toFront();
58350         }
58351         header = ghostPanel.header;
58352         // restore options
58353         if (header) {
58354             header.suspendLayout = true;
58355             Ext.Array.forEach(header.query('tool'), function(tool){
58356                 header.remove(tool);
58357             });
58358             header.suspendLayout = false;
58359         }
58360         ghostPanel.addTool(me.ghostTools());
58361         ghostPanel.setTitle(me.title);
58362         ghostPanel.setIconCls(me.iconCls);
58363
58364         ghostPanel.el.show();
58365         ghostPanel.setPosition(box.x, box.y);
58366         ghostPanel.setSize(box.width, box.height);
58367         me.el.hide();
58368         if (me.floatingItems) {
58369             me.floatingItems.hide();
58370         }
58371         return ghostPanel;
58372     },
58373
58374     // private
58375     unghost: function(show, matchPosition) {
58376         var me = this;
58377         if (!me.ghostPanel) {
58378             return;
58379         }
58380         if (show !== false) {
58381             me.el.show();
58382             if (matchPosition !== false) {
58383                 me.setPosition(me.ghostPanel.getPosition());
58384             }
58385             if (me.floatingItems) {
58386                 me.floatingItems.show();
58387             }
58388             Ext.defer(me.focus, 10, me);
58389         }
58390         me.ghostPanel.el.hide();
58391     },
58392
58393     initResizable: function(resizable) {
58394         if (this.collapsed) {
58395             resizable.disabled = true;
58396         }
58397         this.callParent([resizable]);
58398     }
58399 }, function(){
58400     this.prototype.animCollapse = Ext.enableFx;
58401 });
58402
58403 /**
58404  * Component layout for Tip/ToolTip/etc. components
58405  * @class Ext.layout.component.Tip
58406  * @extends Ext.layout.component.Dock
58407  * @private
58408  */
58409
58410 Ext.define('Ext.layout.component.Tip', {
58411
58412     /* Begin Definitions */
58413
58414     alias: ['layout.tip'],
58415
58416     extend: 'Ext.layout.component.Dock',
58417
58418     /* End Definitions */
58419
58420     type: 'tip',
58421     
58422     onLayout: function(width, height) {
58423         var me = this,
58424             owner = me.owner,
58425             el = owner.el,
58426             minWidth,
58427             maxWidth,
58428             naturalWidth,
58429             constrainedWidth,
58430             xy = el.getXY();
58431
58432         // Position offscreen so the natural width is not affected by the viewport's right edge
58433         el.setXY([-9999,-9999]);
58434
58435         // Calculate initial layout
58436         this.callParent(arguments);
58437
58438         // Handle min/maxWidth for auto-width tips
58439         if (!Ext.isNumber(width)) {
58440             minWidth = owner.minWidth;
58441             maxWidth = owner.maxWidth;
58442             // IE6/7 in strict mode have a problem doing an autoWidth
58443             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
58444                 constrainedWidth = me.doAutoWidth();
58445             } else {
58446                 naturalWidth = el.getWidth();
58447             }
58448             if (naturalWidth < minWidth) {
58449                 constrainedWidth = minWidth;
58450             }
58451             else if (naturalWidth > maxWidth) {
58452                 constrainedWidth = maxWidth;
58453             }
58454             if (constrainedWidth) {
58455                 this.callParent([constrainedWidth, height]);
58456             }
58457         }
58458
58459         // Restore position
58460         el.setXY(xy);
58461     },
58462     
58463     doAutoWidth: function(){
58464         var me = this,
58465             owner = me.owner,
58466             body = owner.body,
58467             width = body.getTextWidth();
58468             
58469         if (owner.header) {
58470             width = Math.max(width, owner.header.getWidth());
58471         }
58472         if (!Ext.isDefined(me.frameWidth)) {
58473             me.frameWidth = owner.el.getWidth() - body.getWidth();
58474         }
58475         width += me.frameWidth + body.getPadding('lr');
58476         return width;
58477     }
58478 });
58479
58480 /**
58481  * @class Ext.tip.Tip
58482  * @extends Ext.panel.Panel
58483  * This is the base class for {@link Ext.tip.QuickTip} and {@link Ext.tip.ToolTip} that provides the basic layout and
58484  * positioning that all tip-based classes require. This class can be used directly for simple, statically-positioned
58485  * tips that are displayed programmatically, or it can be extended to provide custom tip implementations.
58486  * @xtype tip
58487  */
58488 Ext.define('Ext.tip.Tip', {
58489     extend: 'Ext.panel.Panel',
58490     requires: [ 'Ext.layout.component.Tip' ],
58491     alternateClassName: 'Ext.Tip',
58492     /**
58493      * @cfg {Boolean} [closable=false]
58494      * True to render a close tool button into the tooltip header.
58495      */
58496     /**
58497      * @cfg {Number} width
58498      * Width in pixels of the tip (defaults to auto).  Width will be ignored if it exceeds the bounds of
58499      * {@link #minWidth} or {@link #maxWidth}.  The maximum supported value is 500.
58500      */
58501     /**
58502      * @cfg {Number} minWidth The minimum width of the tip in pixels.
58503      */
58504     minWidth : 40,
58505     /**
58506      * @cfg {Number} maxWidth The maximum width of the tip in pixels.  The maximum supported value is 500.
58507      */
58508     maxWidth : 300,
58509     /**
58510      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
58511      * for bottom-right shadow.
58512      */
58513     shadow : "sides",
58514
58515     /**
58516      * @cfg {String} defaultAlign
58517      * <b>Experimental</b>. The default {@link Ext.Element#alignTo} anchor position value for this tip relative
58518      * to its element of origin.
58519      */
58520     defaultAlign : "tl-bl?",
58521     /**
58522      * @cfg {Boolean} constrainPosition
58523      * If true, then the tooltip will be automatically constrained to stay within the browser viewport.
58524      */
58525     constrainPosition : true,
58526
58527     // @inherited
58528     frame: false,
58529
58530     // private panel overrides
58531     autoRender: true,
58532     hidden: true,
58533     baseCls: Ext.baseCSSPrefix + 'tip',
58534     floating: {
58535         shadow: true,
58536         shim: true,
58537         constrain: true
58538     },
58539     focusOnToFront: false,
58540     componentLayout: 'tip',
58541
58542     /**
58543      * @cfg {String} closeAction
58544      * <p>The action to take when the close header tool is clicked:
58545      * <div class="mdetail-params"><ul>
58546      * <li><b><code>'{@link #destroy}'</code></b> : <div class="sub-desc">
58547      * {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy}
58548      * it and all descendant Components. The window will <b>not</b> be available to be
58549      * redisplayed via the {@link #show} method.
58550      * </div></li>
58551      * <li><b><code>'{@link #hide}'</code></b> : <b>Default</b><div class="sub-desc">
58552      * {@link #hide} the window by setting visibility to hidden and applying negative offsets.
58553      * The window will be available to be redisplayed via the {@link #show} method.
58554      * </div></li>
58555      * </ul></div>
58556      * <p><b>Note:</b> This behavior has changed! setting *does* affect the {@link #close} method
58557      * which will invoke the approriate closeAction.
58558      */
58559     closeAction: 'hide',
58560
58561     ariaRole: 'tooltip',
58562
58563     initComponent: function() {
58564         var me = this;
58565
58566         me.floating = Ext.apply({}, {shadow: me.shadow}, me.self.prototype.floating);
58567         me.callParent(arguments);
58568
58569         // Or in the deprecated config. Floating.doConstrain only constrains if the constrain property is truthy.
58570         me.constrain = me.constrain || me.constrainPosition;
58571     },
58572
58573     /**
58574      * Shows this tip at the specified XY position.  Example usage:
58575      * <pre><code>
58576 // Show the tip at x:50 and y:100
58577 tip.showAt([50,100]);
58578 </code></pre>
58579      * @param {Number[]} xy An array containing the x and y coordinates
58580      */
58581     showAt : function(xy){
58582         var me = this;
58583         this.callParent(arguments);
58584         // Show may have been vetoed.
58585         if (me.isVisible()) {
58586             me.setPagePosition(xy[0], xy[1]);
58587             if (me.constrainPosition || me.constrain) {
58588                 me.doConstrain();
58589             }
58590             me.toFront(true);
58591         }
58592     },
58593
58594     /**
58595      * <b>Experimental</b>. Shows this tip at a position relative to another element using a standard {@link Ext.Element#alignTo}
58596      * anchor position value.  Example usage:
58597      * <pre><code>
58598 // Show the tip at the default position ('tl-br?')
58599 tip.showBy('my-el');
58600
58601 // Show the tip's top-left corner anchored to the element's top-right corner
58602 tip.showBy('my-el', 'tl-tr');
58603 </code></pre>
58604      * @param {String/HTMLElement/Ext.Element} el An HTMLElement, Ext.Element or string id of the target element to align to
58605      * @param {String} [position] A valid {@link Ext.Element#alignTo} anchor position (defaults to 'tl-br?' or
58606      * {@link #defaultAlign} if specified).
58607      */
58608     showBy : function(el, pos) {
58609         this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign));
58610     },
58611
58612     /**
58613      * @private
58614      * @override
58615      * Set Tip draggable using base Component's draggability
58616      */
58617     initDraggable : function(){
58618         var me = this;
58619         me.draggable = {
58620             el: me.getDragEl(),
58621             delegate: me.header.el,
58622             constrain: me,
58623             constrainTo: me.el.getScopeParent()
58624         };
58625         // Important: Bypass Panel's initDraggable. Call direct to Component's implementation.
58626         Ext.Component.prototype.initDraggable.call(me);
58627     },
58628
58629     // Tip does not ghost. Drag is "live"
58630     ghost: undefined,
58631     unghost: undefined
58632 });
58633
58634 /**
58635  * ToolTip is a {@link Ext.tip.Tip} implementation that handles the common case of displaying a
58636  * tooltip when hovering over a certain element or elements on the page. It allows fine-grained
58637  * control over the tooltip's alignment relative to the target element or mouse, and the timing
58638  * of when it is automatically shown and hidden.
58639  *
58640  * This implementation does **not** have a built-in method of automatically populating the tooltip's
58641  * text based on the target element; you must either configure a fixed {@link #html} value for each
58642  * ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to
58643  * generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more
58644  * convenient way of automatically populating and configuring a tooltip based on specific DOM
58645  * attributes of each target element.
58646  *
58647  * # Basic Example
58648  *
58649  *     var tip = Ext.create('Ext.tip.ToolTip', {
58650  *         target: 'clearButton',
58651  *         html: 'Press this button to clear the form'
58652  *     });
58653  *
58654  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip1.png Basic Ext.tip.ToolTip}
58655  *
58656  * # Delegation
58657  *
58658  * In addition to attaching a ToolTip to a single element, you can also use delegation to attach
58659  * one ToolTip to many elements under a common parent. This is more efficient than creating many
58660  * ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the
58661  * elements, and then set the {@link #delegate} config to a CSS selector that will select all the
58662  * appropriate sub-elements.
58663  *
58664  * When using delegation, it is likely that you will want to programmatically change the content
58665  * of the ToolTip based on each delegate element; you can do this by implementing a custom
58666  * listener for the {@link #beforeshow} event. Example:
58667  *
58668  *     var store = Ext.create('Ext.data.ArrayStore', {
58669  *         fields: ['company', 'price', 'change'],
58670  *         data: [
58671  *             ['3m Co',                               71.72, 0.02],
58672  *             ['Alcoa Inc',                           29.01, 0.42],
58673  *             ['Altria Group Inc',                    83.81, 0.28],
58674  *             ['American Express Company',            52.55, 0.01],
58675  *             ['American International Group, Inc.',  64.13, 0.31],
58676  *             ['AT&T Inc.',                           31.61, -0.48]
58677  *         ]
58678  *     });
58679  *
58680  *     var grid = Ext.create('Ext.grid.Panel', {
58681  *         title: 'Array Grid',
58682  *         store: store,
58683  *         columns: [
58684  *             {text: 'Company', flex: 1, dataIndex: 'company'},
58685  *             {text: 'Price', width: 75, dataIndex: 'price'},
58686  *             {text: 'Change', width: 75, dataIndex: 'change'}
58687  *         ],
58688  *         height: 200,
58689  *         width: 400,
58690  *         renderTo: Ext.getBody()
58691  *     });
58692  *
58693  *     grid.getView().on('render', function(view) {
58694  *         view.tip = Ext.create('Ext.tip.ToolTip', {
58695  *             // The overall target element.
58696  *             target: view.el,
58697  *             // Each grid row causes its own seperate show and hide.
58698  *             delegate: view.itemSelector,
58699  *             // Moving within the row should not hide the tip.
58700  *             trackMouse: true,
58701  *             // Render immediately so that tip.body can be referenced prior to the first show.
58702  *             renderTo: Ext.getBody(),
58703  *             listeners: {
58704  *                 // Change content dynamically depending on which element triggered the show.
58705  *                 beforeshow: function updateTipBody(tip) {
58706  *                     tip.update('Over company "' + view.getRecord(tip.triggerElement).get('company') + '"');
58707  *                 }
58708  *             }
58709  *         });
58710  *     });
58711  *
58712  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip2.png Ext.tip.ToolTip with delegation}
58713  *
58714  * # Alignment
58715  *
58716  * The following configuration properties allow control over how the ToolTip is aligned relative to
58717  * the target element and/or mouse pointer:
58718  *
58719  * - {@link #anchor}
58720  * - {@link #anchorToTarget}
58721  * - {@link #anchorOffset}
58722  * - {@link #trackMouse}
58723  * - {@link #mouseOffset}
58724  *
58725  * # Showing/Hiding
58726  *
58727  * The following configuration properties allow control over how and when the ToolTip is automatically
58728  * shown and hidden:
58729  *
58730  * - {@link #autoHide}
58731  * - {@link #showDelay}
58732  * - {@link #hideDelay}
58733  * - {@link #dismissDelay}
58734  *
58735  * @docauthor Jason Johnston <jason@sencha.com>
58736  */
58737 Ext.define('Ext.tip.ToolTip', {
58738     extend: 'Ext.tip.Tip',
58739     alias: 'widget.tooltip',
58740     alternateClassName: 'Ext.ToolTip',
58741     /**
58742      * @property {HTMLElement} triggerElement
58743      * When a ToolTip is configured with the `{@link #delegate}`
58744      * option to cause selected child elements of the `{@link #target}`
58745      * Element to each trigger a seperate show event, this property is set to
58746      * the DOM element which triggered the show.
58747      */
58748     /**
58749      * @cfg {HTMLElement/Ext.Element/String} target
58750      * The target element or string id to monitor for mouseover events to trigger
58751      * showing this ToolTip.
58752      */
58753     /**
58754      * @cfg {Boolean} [autoHide=true]
58755      * True to automatically hide the tooltip after the
58756      * mouse exits the target element or after the `{@link #dismissDelay}`
58757      * has expired if set.  If `{@link #closable} = true`
58758      * a close tool button will be rendered into the tooltip header.
58759      */
58760     /**
58761      * @cfg {Number} showDelay
58762      * Delay in milliseconds before the tooltip displays after the mouse enters the target element.
58763      */
58764     showDelay: 500,
58765     /**
58766      * @cfg {Number} hideDelay
58767      * Delay in milliseconds after the mouse exits the target element but before the tooltip actually hides.
58768      * Set to 0 for the tooltip to hide immediately.
58769      */
58770     hideDelay: 200,
58771     /**
58772      * @cfg {Number} dismissDelay
58773      * Delay in milliseconds before the tooltip automatically hides. To disable automatic hiding, set
58774      * dismissDelay = 0.
58775      */
58776     dismissDelay: 5000,
58777     /**
58778      * @cfg {Number[]} [mouseOffset=[15,18]]
58779      * An XY offset from the mouse position where the tooltip should be shown.
58780      */
58781     /**
58782      * @cfg {Boolean} trackMouse
58783      * True to have the tooltip follow the mouse as it moves over the target element.
58784      */
58785     trackMouse: false,
58786     /**
58787      * @cfg {String} anchor
58788      * If specified, indicates that the tip should be anchored to a
58789      * particular side of the target element or mouse pointer ("top", "right", "bottom",
58790      * or "left"), with an arrow pointing back at the target or mouse pointer. If
58791      * {@link #constrainPosition} is enabled, this will be used as a preferred value
58792      * only and may be flipped as needed.
58793      */
58794     /**
58795      * @cfg {Boolean} anchorToTarget
58796      * True to anchor the tooltip to the target element, false to anchor it relative to the mouse coordinates.
58797      * When `anchorToTarget` is true, use `{@link #defaultAlign}` to control tooltip alignment to the
58798      * target element.  When `anchorToTarget` is false, use `{@link #anchor}` instead to control alignment.
58799      */
58800     anchorToTarget: true,
58801     /**
58802      * @cfg {Number} anchorOffset
58803      * A numeric pixel value used to offset the default position of the anchor arrow.  When the anchor
58804      * position is on the top or bottom of the tooltip, `anchorOffset` will be used as a horizontal offset.
58805      * Likewise, when the anchor position is on the left or right side, `anchorOffset` will be used as
58806      * a vertical offset.
58807      */
58808     anchorOffset: 0,
58809     /**
58810      * @cfg {String} delegate
58811      *
58812      * A {@link Ext.DomQuery DomQuery} selector which allows selection of individual elements within the
58813      * `{@link #target}` element to trigger showing and hiding the ToolTip as the mouse moves within the
58814      * target.
58815      *
58816      * When specified, the child element of the target which caused a show event is placed into the
58817      * `{@link #triggerElement}` property before the ToolTip is shown.
58818      *
58819      * This may be useful when a Component has regular, repeating elements in it, each of which need a
58820      * ToolTip which contains information specific to that element.
58821      *
58822      * See the delegate example in class documentation of {@link Ext.tip.ToolTip}.
58823      */
58824
58825     // private
58826     targetCounter: 0,
58827     quickShowInterval: 250,
58828
58829     // private
58830     initComponent: function() {
58831         var me = this;
58832         me.callParent(arguments);
58833         me.lastActive = new Date();
58834         me.setTarget(me.target);
58835         me.origAnchor = me.anchor;
58836     },
58837
58838     // private
58839     onRender: function(ct, position) {
58840         var me = this;
58841         me.callParent(arguments);
58842         me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
58843         me.anchorEl = me.el.createChild({
58844             cls: Ext.baseCSSPrefix + 'tip-anchor ' + me.anchorCls
58845         });
58846     },
58847
58848     // private
58849     afterRender: function() {
58850         var me = this,
58851             zIndex;
58852
58853         me.callParent(arguments);
58854         zIndex = parseInt(me.el.getZIndex(), 10) || 0;
58855         me.anchorEl.setStyle('z-index', zIndex + 1).setVisibilityMode(Ext.Element.DISPLAY);
58856     },
58857
58858     /**
58859      * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
58860      * @param {String/HTMLElement/Ext.Element} t The Element, HtmlElement, or ID of an element to bind to
58861      */
58862     setTarget: function(target) {
58863         var me = this,
58864             t = Ext.get(target),
58865             tg;
58866
58867         if (me.target) {
58868             tg = Ext.get(me.target);
58869             me.mun(tg, 'mouseover', me.onTargetOver, me);
58870             me.mun(tg, 'mouseout', me.onTargetOut, me);
58871             me.mun(tg, 'mousemove', me.onMouseMove, me);
58872         }
58873
58874         me.target = t;
58875         if (t) {
58876
58877             me.mon(t, {
58878                 // TODO - investigate why IE6/7 seem to fire recursive resize in e.getXY
58879                 // breaking QuickTip#onTargetOver (EXTJSIV-1608)
58880                 freezeEvent: true,
58881
58882                 mouseover: me.onTargetOver,
58883                 mouseout: me.onTargetOut,
58884                 mousemove: me.onMouseMove,
58885                 scope: me
58886             });
58887         }
58888         if (me.anchor) {
58889             me.anchorTarget = me.target;
58890         }
58891     },
58892
58893     // private
58894     onMouseMove: function(e) {
58895         var me = this,
58896             t = me.delegate ? e.getTarget(me.delegate) : me.triggerElement = true,
58897             xy;
58898         if (t) {
58899             me.targetXY = e.getXY();
58900             if (t === me.triggerElement) {
58901                 if (!me.hidden && me.trackMouse) {
58902                     xy = me.getTargetXY();
58903                     if (me.constrainPosition) {
58904                         xy = me.el.adjustForConstraints(xy, me.el.getScopeParent());
58905                     }
58906                     me.setPagePosition(xy);
58907                 }
58908             } else {
58909                 me.hide();
58910                 me.lastActive = new Date(0);
58911                 me.onTargetOver(e);
58912             }
58913         } else if ((!me.closable && me.isVisible()) && me.autoHide !== false) {
58914             me.hide();
58915         }
58916     },
58917
58918     // private
58919     getTargetXY: function() {
58920         var me = this,
58921             mouseOffset;
58922         if (me.delegate) {
58923             me.anchorTarget = me.triggerElement;
58924         }
58925         if (me.anchor) {
58926             me.targetCounter++;
58927                 var offsets = me.getOffsets(),
58928                     xy = (me.anchorToTarget && !me.trackMouse) ? me.el.getAlignToXY(me.anchorTarget, me.getAnchorAlign()) : me.targetXY,
58929                     dw = Ext.Element.getViewWidth() - 5,
58930                     dh = Ext.Element.getViewHeight() - 5,
58931                     de = document.documentElement,
58932                     bd = document.body,
58933                     scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5,
58934                     scrollY = (de.scrollTop || bd.scrollTop || 0) + 5,
58935                     axy = [xy[0] + offsets[0], xy[1] + offsets[1]],
58936                     sz = me.getSize(),
58937                     constrainPosition = me.constrainPosition;
58938
58939             me.anchorEl.removeCls(me.anchorCls);
58940
58941             if (me.targetCounter < 2 && constrainPosition) {
58942                 if (axy[0] < scrollX) {
58943                     if (me.anchorToTarget) {
58944                         me.defaultAlign = 'l-r';
58945                         if (me.mouseOffset) {
58946                             me.mouseOffset[0] *= -1;
58947                         }
58948                     }
58949                     me.anchor = 'left';
58950                     return me.getTargetXY();
58951                 }
58952                 if (axy[0] + sz.width > dw) {
58953                     if (me.anchorToTarget) {
58954                         me.defaultAlign = 'r-l';
58955                         if (me.mouseOffset) {
58956                             me.mouseOffset[0] *= -1;
58957                         }
58958                     }
58959                     me.anchor = 'right';
58960                     return me.getTargetXY();
58961                 }
58962                 if (axy[1] < scrollY) {
58963                     if (me.anchorToTarget) {
58964                         me.defaultAlign = 't-b';
58965                         if (me.mouseOffset) {
58966                             me.mouseOffset[1] *= -1;
58967                         }
58968                     }
58969                     me.anchor = 'top';
58970                     return me.getTargetXY();
58971                 }
58972                 if (axy[1] + sz.height > dh) {
58973                     if (me.anchorToTarget) {
58974                         me.defaultAlign = 'b-t';
58975                         if (me.mouseOffset) {
58976                             me.mouseOffset[1] *= -1;
58977                         }
58978                     }
58979                     me.anchor = 'bottom';
58980                     return me.getTargetXY();
58981                 }
58982             }
58983
58984             me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
58985             me.anchorEl.addCls(me.anchorCls);
58986             me.targetCounter = 0;
58987             return axy;
58988         } else {
58989             mouseOffset = me.getMouseOffset();
58990             return (me.targetXY) ? [me.targetXY[0] + mouseOffset[0], me.targetXY[1] + mouseOffset[1]] : mouseOffset;
58991         }
58992     },
58993
58994     getMouseOffset: function() {
58995         var me = this,
58996         offset = me.anchor ? [0, 0] : [15, 18];
58997         if (me.mouseOffset) {
58998             offset[0] += me.mouseOffset[0];
58999             offset[1] += me.mouseOffset[1];
59000         }
59001         return offset;
59002     },
59003
59004     // private
59005     getAnchorPosition: function() {
59006         var me = this,
59007             m;
59008         if (me.anchor) {
59009             me.tipAnchor = me.anchor.charAt(0);
59010         } else {
59011             m = me.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
59012             if (!m) {
59013                 Ext.Error.raise('The AnchorTip.defaultAlign value "' + me.defaultAlign + '" is invalid.');
59014             }
59015             me.tipAnchor = m[1].charAt(0);
59016         }
59017
59018         switch (me.tipAnchor) {
59019         case 't':
59020             return 'top';
59021         case 'b':
59022             return 'bottom';
59023         case 'r':
59024             return 'right';
59025         }
59026         return 'left';
59027     },
59028
59029     // private
59030     getAnchorAlign: function() {
59031         switch (this.anchor) {
59032         case 'top':
59033             return 'tl-bl';
59034         case 'left':
59035             return 'tl-tr';
59036         case 'right':
59037             return 'tr-tl';
59038         default:
59039             return 'bl-tl';
59040         }
59041     },
59042
59043     // private
59044     getOffsets: function() {
59045         var me = this,
59046             mouseOffset,
59047             offsets,
59048             ap = me.getAnchorPosition().charAt(0);
59049         if (me.anchorToTarget && !me.trackMouse) {
59050             switch (ap) {
59051             case 't':
59052                 offsets = [0, 9];
59053                 break;
59054             case 'b':
59055                 offsets = [0, -13];
59056                 break;
59057             case 'r':
59058                 offsets = [ - 13, 0];
59059                 break;
59060             default:
59061                 offsets = [9, 0];
59062                 break;
59063             }
59064         } else {
59065             switch (ap) {
59066             case 't':
59067                 offsets = [ - 15 - me.anchorOffset, 30];
59068                 break;
59069             case 'b':
59070                 offsets = [ - 19 - me.anchorOffset, -13 - me.el.dom.offsetHeight];
59071                 break;
59072             case 'r':
59073                 offsets = [ - 15 - me.el.dom.offsetWidth, -13 - me.anchorOffset];
59074                 break;
59075             default:
59076                 offsets = [25, -13 - me.anchorOffset];
59077                 break;
59078             }
59079         }
59080         mouseOffset = me.getMouseOffset();
59081         offsets[0] += mouseOffset[0];
59082         offsets[1] += mouseOffset[1];
59083
59084         return offsets;
59085     },
59086
59087     // private
59088     onTargetOver: function(e) {
59089         var me = this,
59090             t;
59091
59092         if (me.disabled || e.within(me.target.dom, true)) {
59093             return;
59094         }
59095         t = e.getTarget(me.delegate);
59096         if (t) {
59097             me.triggerElement = t;
59098             me.clearTimer('hide');
59099             me.targetXY = e.getXY();
59100             me.delayShow();
59101         }
59102     },
59103
59104     // private
59105     delayShow: function() {
59106         var me = this;
59107         if (me.hidden && !me.showTimer) {
59108             if (Ext.Date.getElapsed(me.lastActive) < me.quickShowInterval) {
59109                 me.show();
59110             } else {
59111                 me.showTimer = Ext.defer(me.show, me.showDelay, me);
59112             }
59113         }
59114         else if (!me.hidden && me.autoHide !== false) {
59115             me.show();
59116         }
59117     },
59118
59119     // private
59120     onTargetOut: function(e) {
59121         var me = this;
59122         if (me.disabled || e.within(me.target.dom, true)) {
59123             return;
59124         }
59125         me.clearTimer('show');
59126         if (me.autoHide !== false) {
59127             me.delayHide();
59128         }
59129     },
59130
59131     // private
59132     delayHide: function() {
59133         var me = this;
59134         if (!me.hidden && !me.hideTimer) {
59135             me.hideTimer = Ext.defer(me.hide, me.hideDelay, me);
59136         }
59137     },
59138
59139     /**
59140      * Hides this tooltip if visible.
59141      */
59142     hide: function() {
59143         var me = this;
59144         me.clearTimer('dismiss');
59145         me.lastActive = new Date();
59146         if (me.anchorEl) {
59147             me.anchorEl.hide();
59148         }
59149         me.callParent(arguments);
59150         delete me.triggerElement;
59151     },
59152
59153     /**
59154      * Shows this tooltip at the current event target XY position.
59155      */
59156     show: function() {
59157         var me = this;
59158
59159         // Show this Component first, so that sizing can be calculated
59160         // pre-show it off screen so that the el will have dimensions
59161         this.callParent();
59162         if (this.hidden === false) {
59163             me.setPagePosition(-10000, -10000);
59164
59165             if (me.anchor) {
59166                 me.anchor = me.origAnchor;
59167             }
59168             me.showAt(me.getTargetXY());
59169
59170             if (me.anchor) {
59171                 me.syncAnchor();
59172                 me.anchorEl.show();
59173             } else {
59174                 me.anchorEl.hide();
59175             }
59176         }
59177     },
59178
59179     // inherit docs
59180     showAt: function(xy) {
59181         var me = this;
59182         me.lastActive = new Date();
59183         me.clearTimers();
59184
59185         // Only call if this is hidden. May have been called from show above.
59186         if (!me.isVisible()) {
59187             this.callParent(arguments);
59188         }
59189
59190         // Show may have been vetoed.
59191         if (me.isVisible()) {
59192             me.setPagePosition(xy[0], xy[1]);
59193             if (me.constrainPosition || me.constrain) {
59194                 me.doConstrain();
59195             }
59196             me.toFront(true);
59197         }
59198
59199         if (me.dismissDelay && me.autoHide !== false) {
59200             me.dismissTimer = Ext.defer(me.hide, me.dismissDelay, me);
59201         }
59202         if (me.anchor) {
59203             me.syncAnchor();
59204             if (!me.anchorEl.isVisible()) {
59205                 me.anchorEl.show();
59206             }
59207         } else {
59208             me.anchorEl.hide();
59209         }
59210     },
59211
59212     // private
59213     syncAnchor: function() {
59214         var me = this,
59215             anchorPos,
59216             targetPos,
59217             offset;
59218         switch (me.tipAnchor.charAt(0)) {
59219         case 't':
59220             anchorPos = 'b';
59221             targetPos = 'tl';
59222             offset = [20 + me.anchorOffset, 1];
59223             break;
59224         case 'r':
59225             anchorPos = 'l';
59226             targetPos = 'tr';
59227             offset = [ - 1, 12 + me.anchorOffset];
59228             break;
59229         case 'b':
59230             anchorPos = 't';
59231             targetPos = 'bl';
59232             offset = [20 + me.anchorOffset, -1];
59233             break;
59234         default:
59235             anchorPos = 'r';
59236             targetPos = 'tl';
59237             offset = [1, 12 + me.anchorOffset];
59238             break;
59239         }
59240         me.anchorEl.alignTo(me.el, anchorPos + '-' + targetPos, offset);
59241     },
59242
59243     // private
59244     setPagePosition: function(x, y) {
59245         var me = this;
59246         me.callParent(arguments);
59247         if (me.anchor) {
59248             me.syncAnchor();
59249         }
59250     },
59251
59252     // private
59253     clearTimer: function(name) {
59254         name = name + 'Timer';
59255         clearTimeout(this[name]);
59256         delete this[name];
59257     },
59258
59259     // private
59260     clearTimers: function() {
59261         var me = this;
59262         me.clearTimer('show');
59263         me.clearTimer('dismiss');
59264         me.clearTimer('hide');
59265     },
59266
59267     // private
59268     onShow: function() {
59269         var me = this;
59270         me.callParent();
59271         me.mon(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
59272     },
59273
59274     // private
59275     onHide: function() {
59276         var me = this;
59277         me.callParent();
59278         me.mun(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
59279     },
59280
59281     // private
59282     onDocMouseDown: function(e) {
59283         var me = this;
59284         if (me.autoHide !== true && !me.closable && !e.within(me.el.dom)) {
59285             me.disable();
59286             Ext.defer(me.doEnable, 100, me);
59287         }
59288     },
59289
59290     // private
59291     doEnable: function() {
59292         if (!this.isDestroyed) {
59293             this.enable();
59294         }
59295     },
59296
59297     // private
59298     onDisable: function() {
59299         this.callParent();
59300         this.clearTimers();
59301         this.hide();
59302     },
59303
59304     beforeDestroy: function() {
59305         var me = this;
59306         me.clearTimers();
59307         Ext.destroy(me.anchorEl);
59308         delete me.anchorEl;
59309         delete me.target;
59310         delete me.anchorTarget;
59311         delete me.triggerElement;
59312         me.callParent();
59313     },
59314
59315     // private
59316     onDestroy: function() {
59317         Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
59318         this.callParent();
59319     }
59320 });
59321
59322 /**
59323  * @class Ext.tip.QuickTip
59324  * @extends Ext.tip.ToolTip
59325  * A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global
59326  * {@link Ext.tip.QuickTipManager} instance.  See the QuickTipManager documentation for additional usage details and examples.
59327  * @xtype quicktip
59328  */
59329 Ext.define('Ext.tip.QuickTip', {
59330     extend: 'Ext.tip.ToolTip',
59331     alternateClassName: 'Ext.QuickTip',
59332     /**
59333      * @cfg {String/HTMLElement/Ext.Element} target The target HTMLElement, Ext.Element or id to associate with this Quicktip (defaults to the document).
59334      */
59335     /**
59336      * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available.
59337      */
59338     interceptTitles : false,
59339
59340     // Force creation of header Component
59341     title: '&#160;',
59342
59343     // private
59344     tagConfig : {
59345         namespace : "data-",
59346         attribute : "qtip",
59347         width : "qwidth",
59348         target : "target",
59349         title : "qtitle",
59350         hide : "hide",
59351         cls : "qclass",
59352         align : "qalign",
59353         anchor : "anchor"
59354     },
59355
59356     // private
59357     initComponent : function(){
59358         var me = this;
59359
59360         me.target = me.target || Ext.getDoc();
59361         me.targets = me.targets || {};
59362         me.callParent();
59363     },
59364
59365     /**
59366      * Configures a new quick tip instance and assigns it to a target element.  The following config values are
59367      * supported (for example usage, see the {@link Ext.tip.QuickTipManager} class header):
59368      * <div class="mdetail-params"><ul>
59369      * <li>autoHide</li>
59370      * <li>cls</li>
59371      * <li>dismissDelay (overrides the singleton value)</li>
59372      * <li>target (required)</li>
59373      * <li>text (required)</li>
59374      * <li>title</li>
59375      * <li>width</li></ul></div>
59376      * @param {Object} config The config object
59377      */
59378     register : function(config){
59379         var configs = Ext.isArray(config) ? config : arguments,
59380             i = 0,
59381             len = configs.length,
59382             target, j, targetLen;
59383
59384         for (; i < len; i++) {
59385             config = configs[i];
59386             target = config.target;
59387             if (target) {
59388                 if (Ext.isArray(target)) {
59389                     for (j = 0, targetLen = target.length; j < targetLen; j++) {
59390                         this.targets[Ext.id(target[j])] = config;
59391                     }
59392                 } else{
59393                     this.targets[Ext.id(target)] = config;
59394                 }
59395             }
59396         }
59397     },
59398
59399     /**
59400      * Removes this quick tip from its element and destroys it.
59401      * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
59402      */
59403     unregister : function(el){
59404         delete this.targets[Ext.id(el)];
59405     },
59406
59407     /**
59408      * Hides a visible tip or cancels an impending show for a particular element.
59409      * @param {String/HTMLElement/Ext.Element} el The element that is the target of the tip or ID of the element.
59410      */
59411     cancelShow: function(el){
59412         var me = this,
59413             activeTarget = me.activeTarget;
59414
59415         el = Ext.get(el).dom;
59416         if (me.isVisible()) {
59417             if (activeTarget && activeTarget.el == el) {
59418                 me.hide();
59419             }
59420         } else if (activeTarget && activeTarget.el == el) {
59421             me.clearTimer('show');
59422         }
59423     },
59424
59425     /**
59426      * @private
59427      * Reads the tip text from the closest node to the event target which contains the attribute we
59428      * are configured to look for. Returns an object containing the text from the attribute, and the target element from
59429      * which the text was read.
59430      */
59431     getTipCfg: function(e) {
59432         var t = e.getTarget(),
59433             titleText = t.title,
59434             cfg;
59435
59436         if (this.interceptTitles && titleText && Ext.isString(titleText)) {
59437             t.qtip = titleText;
59438             t.removeAttribute("title");
59439             e.preventDefault();
59440             return {
59441                 text: titleText
59442             };
59443         }
59444         else {
59445             cfg = this.tagConfig;
59446             t = e.getTarget('[' + cfg.namespace + cfg.attribute + ']');
59447             if (t) {
59448                 return {
59449                     target: t,
59450                     text: t.getAttribute(cfg.namespace + cfg.attribute)
59451                 };
59452             }
59453         }
59454     },
59455
59456     // private
59457     onTargetOver : function(e){
59458         var me = this,
59459             target = e.getTarget(),
59460             elTarget,
59461             cfg,
59462             ns,
59463             tipConfig,
59464             autoHide;
59465
59466         if (me.disabled) {
59467             return;
59468         }
59469
59470         // TODO - this causes "e" to be recycled in IE6/7 (EXTJSIV-1608) so ToolTip#setTarget
59471         // was changed to include freezeEvent. The issue seems to be a nested 'resize' event
59472         // that smashed Ext.EventObject.
59473         me.targetXY = e.getXY();
59474
59475         if(!target || target.nodeType !== 1 || target == document || target == document.body){
59476             return;
59477         }
59478
59479         if (me.activeTarget && ((target == me.activeTarget.el) || Ext.fly(me.activeTarget.el).contains(target))) {
59480             me.clearTimer('hide');
59481             me.show();
59482             return;
59483         }
59484
59485         if (target) {
59486             Ext.Object.each(me.targets, function(key, value) {
59487                 var targetEl = Ext.fly(value.target);
59488                 if (targetEl && (targetEl.dom === target || targetEl.contains(target))) {
59489                     elTarget = targetEl.dom;
59490                     return false;
59491                 }
59492             });
59493             if (elTarget) {
59494                 me.activeTarget = me.targets[elTarget.id];
59495                 me.activeTarget.el = target;
59496                 me.anchor = me.activeTarget.anchor;
59497                 if (me.anchor) {
59498                     me.anchorTarget = target;
59499                 }
59500                 me.delayShow();
59501                 return;
59502             }
59503         }
59504
59505         elTarget = Ext.get(target);
59506         cfg = me.tagConfig;
59507         ns = cfg.namespace;
59508         tipConfig = me.getTipCfg(e);
59509
59510         if (tipConfig) {
59511
59512             // getTipCfg may look up the parentNode axis for a tip text attribute and will return the new target node.
59513             // Change our target element to match that from which the tip text attribute was read.
59514             if (tipConfig.target) {
59515                 target = tipConfig.target;
59516                 elTarget = Ext.get(target);
59517             }
59518             autoHide = elTarget.getAttribute(ns + cfg.hide);
59519
59520             me.activeTarget = {
59521                 el: target,
59522                 text: tipConfig.text,
59523                 width: +elTarget.getAttribute(ns + cfg.width) || null,
59524                 autoHide: autoHide != "user" && autoHide !== 'false',
59525                 title: elTarget.getAttribute(ns + cfg.title),
59526                 cls: elTarget.getAttribute(ns + cfg.cls),
59527                 align: elTarget.getAttribute(ns + cfg.align)
59528
59529             };
59530             me.anchor = elTarget.getAttribute(ns + cfg.anchor);
59531             if (me.anchor) {
59532                 me.anchorTarget = target;
59533             }
59534             me.delayShow();
59535         }
59536     },
59537
59538     // private
59539     onTargetOut : function(e){
59540         var me = this;
59541
59542         // If moving within the current target, and it does not have a new tip, ignore the mouseout
59543         if (me.activeTarget && e.within(me.activeTarget.el) && !me.getTipCfg(e)) {
59544             return;
59545         }
59546
59547         me.clearTimer('show');
59548         if (me.autoHide !== false) {
59549             me.delayHide();
59550         }
59551     },
59552
59553     // inherit docs
59554     showAt : function(xy){
59555         var me = this,
59556             target = me.activeTarget;
59557
59558         if (target) {
59559             if (!me.rendered) {
59560                 me.render(Ext.getBody());
59561                 me.activeTarget = target;
59562             }
59563             if (target.title) {
59564                 me.setTitle(target.title || '');
59565                 me.header.show();
59566             } else {
59567                 me.header.hide();
59568             }
59569             me.body.update(target.text);
59570             me.autoHide = target.autoHide;
59571             me.dismissDelay = target.dismissDelay || me.dismissDelay;
59572             if (me.lastCls) {
59573                 me.el.removeCls(me.lastCls);
59574                 delete me.lastCls;
59575             }
59576             if (target.cls) {
59577                 me.el.addCls(target.cls);
59578                 me.lastCls = target.cls;
59579             }
59580
59581             me.setWidth(target.width);
59582
59583             if (me.anchor) {
59584                 me.constrainPosition = false;
59585             } else if (target.align) { // TODO: this doesn't seem to work consistently
59586                 xy = me.el.getAlignToXY(target.el, target.align);
59587                 me.constrainPosition = false;
59588             }else{
59589                 me.constrainPosition = true;
59590             }
59591         }
59592         me.callParent([xy]);
59593     },
59594
59595     // inherit docs
59596     hide: function(){
59597         delete this.activeTarget;
59598         this.callParent();
59599     }
59600 });
59601
59602 /**
59603  * @class Ext.tip.QuickTipManager
59604  *
59605  * Provides attractive and customizable tooltips for any element. The QuickTips
59606  * singleton is used to configure and manage tooltips globally for multiple elements
59607  * in a generic manner.  To create individual tooltips with maximum customizability,
59608  * you should consider either {@link Ext.tip.Tip} or {@link Ext.tip.ToolTip}.
59609  *
59610  * Quicktips can be configured via tag attributes directly in markup, or by
59611  * registering quick tips programmatically via the {@link #register} method.
59612  *
59613  * The singleton's instance of {@link Ext.tip.QuickTip} is available via
59614  * {@link #getQuickTip}, and supports all the methods, and all the all the
59615  * configuration properties of Ext.tip.QuickTip. These settings will apply to all
59616  * tooltips shown by the singleton.
59617  *
59618  * Below is the summary of the configuration properties which can be used.
59619  * For detailed descriptions see the config options for the {@link Ext.tip.QuickTip QuickTip} class
59620  *
59621  * ## QuickTips singleton configs (all are optional)
59622  *
59623  *  - `dismissDelay`
59624  *  - `hideDelay`
59625  *  - `maxWidth`
59626  *  - `minWidth`
59627  *  - `showDelay`
59628  *  - `trackMouse`
59629  *
59630  * ## Target element configs (optional unless otherwise noted)
59631  *
59632  *  - `autoHide`
59633  *  - `cls`
59634  *  - `dismissDelay` (overrides singleton value)
59635  *  - `target` (required)
59636  *  - `text` (required)
59637  *  - `title`
59638  *  - `width`
59639  *
59640  * Here is an example showing how some of these config options could be used:
59641  *
59642  *     @example
59643  *     // Init the singleton.  Any tag-based quick tips will start working.
59644  *     Ext.tip.QuickTipManager.init();
59645  *
59646  *     // Apply a set of config properties to the singleton
59647  *     Ext.apply(Ext.tip.QuickTipManager.getQuickTip(), {
59648  *         maxWidth: 200,
59649  *         minWidth: 100,
59650  *         showDelay: 50      // Show 50ms after entering target
59651  *     });
59652  *
59653  *     // Create a small panel to add a quick tip to
59654  *     Ext.create('Ext.container.Container', {
59655  *         id: 'quickTipContainer',
59656  *         width: 200,
59657  *         height: 150,
59658  *         style: {
59659  *             backgroundColor:'#000000'
59660  *         },
59661  *         renderTo: Ext.getBody()
59662  *     });
59663  *
59664  *
59665  *     // Manually register a quick tip for a specific element
59666  *     Ext.tip.QuickTipManager.register({
59667  *         target: 'quickTipContainer',
59668  *         title: 'My Tooltip',
59669  *         text: 'This tooltip was added in code',
59670  *         width: 100,
59671  *         dismissDelay: 10000 // Hide after 10 seconds hover
59672  *     });
59673  *
59674  * To register a quick tip in markup, you simply add one or more of the valid QuickTip attributes prefixed with
59675  * the **data-** namespace.  The HTML element itself is automatically set as the quick tip target. Here is the summary
59676  * of supported attributes (optional unless otherwise noted):
59677  *
59678  *  - `hide`: Specifying "user" is equivalent to setting autoHide = false.  Any other value will be the same as autoHide = true.
59679  *  - `qclass`: A CSS class to be applied to the quick tip (equivalent to the 'cls' target element config).
59680  *  - `qtip (required)`: The quick tip text (equivalent to the 'text' target element config).
59681  *  - `qtitle`: The quick tip title (equivalent to the 'title' target element config).
59682  *  - `qwidth`: The quick tip width (equivalent to the 'width' target element config).
59683  *
59684  * Here is an example of configuring an HTML element to display a tooltip from markup:
59685  *
59686  *     // Add a quick tip to an HTML button
59687  *     <input type="button" value="OK" data-qtitle="OK Button" data-qwidth="100"
59688  *          data-qtip="This is a quick tip from markup!"></input>
59689  *
59690  * @singleton
59691  */
59692 Ext.define('Ext.tip.QuickTipManager', function() {
59693     var tip,
59694         disabled = false;
59695
59696     return {
59697         requires: ['Ext.tip.QuickTip'],
59698         singleton: true,
59699         alternateClassName: 'Ext.QuickTips',
59700
59701         /**
59702          * Initialize the global QuickTips instance and prepare any quick tips.
59703          * @param {Boolean} autoRender (optional) True to render the QuickTips container immediately to
59704          * preload images. (Defaults to true)
59705          * @param {Object} config (optional) config object for the created QuickTip. By
59706          * default, the {@link Ext.tip.QuickTip QuickTip} class is instantiated, but this can
59707          * be changed by supplying an xtype property or a className property in this object.
59708          * All other properties on this object are configuration for the created component.
59709          */
59710         init : function (autoRender, config) {
59711             if (!tip) {
59712                 if (!Ext.isReady) {
59713                     Ext.onReady(function(){
59714                         Ext.tip.QuickTipManager.init(autoRender);
59715                     });
59716                     return;
59717                 }
59718
59719                 var tipConfig = Ext.apply({ disabled: disabled }, config),
59720                     className = tipConfig.className,
59721                     xtype = tipConfig.xtype;
59722
59723                 if (className) {
59724                     delete tipConfig.className;
59725                 } else if (xtype) {
59726                     className = 'widget.' + xtype;
59727                     delete tipConfig.xtype;
59728                 }
59729
59730                 if (autoRender !== false) {
59731                     tipConfig.renderTo = document.body;
59732
59733                     if (tipConfig.renderTo.tagName != 'BODY') { // e.g., == 'FRAMESET'
59734                         Ext.Error.raise({
59735                             sourceClass: 'Ext.tip.QuickTipManager',
59736                             sourceMethod: 'init',
59737                             msg: 'Cannot init QuickTipManager: no document body'
59738                         });
59739                     }
59740                 }
59741
59742                 tip = Ext.create(className || 'Ext.tip.QuickTip', tipConfig);
59743             }
59744         },
59745
59746         /**
59747          * Destroy the QuickTips instance.
59748          */
59749         destroy: function() {
59750             if (tip) {
59751                 var undef;
59752                 tip.destroy();
59753                 tip = undef;
59754             }
59755         },
59756
59757         // Protected method called by the dd classes
59758         ddDisable : function(){
59759             // don't disable it if we don't need to
59760             if(tip && !disabled){
59761                 tip.disable();
59762             }
59763         },
59764
59765         // Protected method called by the dd classes
59766         ddEnable : function(){
59767             // only enable it if it hasn't been disabled
59768             if(tip && !disabled){
59769                 tip.enable();
59770             }
59771         },
59772
59773         /**
59774          * Enable quick tips globally.
59775          */
59776         enable : function(){
59777             if(tip){
59778                 tip.enable();
59779             }
59780             disabled = false;
59781         },
59782
59783         /**
59784          * Disable quick tips globally.
59785          */
59786         disable : function(){
59787             if(tip){
59788                 tip.disable();
59789             }
59790             disabled = true;
59791         },
59792
59793         /**
59794          * Returns true if quick tips are enabled, else false.
59795          * @return {Boolean}
59796          */
59797         isEnabled : function(){
59798             return tip !== undefined && !tip.disabled;
59799         },
59800
59801         /**
59802          * Gets the single {@link Ext.tip.QuickTip QuickTip} instance used to show tips from all registered elements.
59803          * @return {Ext.tip.QuickTip}
59804          */
59805         getQuickTip : function(){
59806             return tip;
59807         },
59808
59809         /**
59810          * Configures a new quick tip instance and assigns it to a target element.  See
59811          * {@link Ext.tip.QuickTip#register} for details.
59812          * @param {Object} config The config object
59813          */
59814         register : function(){
59815             tip.register.apply(tip, arguments);
59816         },
59817
59818         /**
59819          * Removes any registered quick tip from the target element and destroys it.
59820          * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
59821          */
59822         unregister : function(){
59823             tip.unregister.apply(tip, arguments);
59824         },
59825
59826         /**
59827          * Alias of {@link #register}.
59828          * @param {Object} config The config object
59829          */
59830         tips : function(){
59831             tip.register.apply(tip, arguments);
59832         }
59833     };
59834 }());
59835 /**
59836  * Represents an Ext JS 4 application, which is typically a single page app using a {@link Ext.container.Viewport Viewport}.
59837  * A typical Ext.app.Application might look like this:
59838  *
59839  *     Ext.application({
59840  *         name: 'MyApp',
59841  *         launch: function() {
59842  *             Ext.create('Ext.container.Viewport', {
59843  *                 items: {
59844  *                     html: 'My App'
59845  *                 }
59846  *             });
59847  *         }
59848  *     });
59849  *
59850  * This does several things. First it creates a global variable called 'MyApp' - all of your Application's classes (such
59851  * as its Models, Views and Controllers) will reside under this single namespace, which drastically lowers the chances
59852  * of colliding global variables.
59853  *
59854  * When the page is ready and all of your JavaScript has loaded, your Application's {@link #launch} function is called,
59855  * at which time you can run the code that starts your app. Usually this consists of creating a Viewport, as we do in
59856  * the example above.
59857  *
59858  * # Telling Application about the rest of the app
59859  *
59860  * Because an Ext.app.Application represents an entire app, we should tell it about the other parts of the app - namely
59861  * the Models, Views and Controllers that are bundled with the application. Let's say we have a blog management app; we
59862  * might have Models and Controllers for Posts and Comments, and Views for listing, adding and editing Posts and Comments.
59863  * Here's how we'd tell our Application about all these things:
59864  *
59865  *     Ext.application({
59866  *         name: 'Blog',
59867  *         models: ['Post', 'Comment'],
59868  *         controllers: ['Posts', 'Comments'],
59869  *
59870  *         launch: function() {
59871  *             ...
59872  *         }
59873  *     });
59874  *
59875  * Note that we didn't actually list the Views directly in the Application itself. This is because Views are managed by
59876  * Controllers, so it makes sense to keep those dependencies there. The Application will load each of the specified
59877  * Controllers using the pathing conventions laid out in the [application architecture guide][mvc] -
59878  * in this case expecting the controllers to reside in `app/controller/Posts.js` and
59879  * `app/controller/Comments.js`. In turn, each Controller simply needs to list the Views it uses and they will be
59880  * automatically loaded. Here's how our Posts controller like be defined:
59881  *
59882  *     Ext.define('MyApp.controller.Posts', {
59883  *         extend: 'Ext.app.Controller',
59884  *         views: ['posts.List', 'posts.Edit'],
59885  *
59886  *         //the rest of the Controller here
59887  *     });
59888  *
59889  * Because we told our Application about our Models and Controllers, and our Controllers about their Views, Ext JS will
59890  * automatically load all of our app files for us. This means we don't have to manually add script tags into our html
59891  * files whenever we add a new class, but more importantly it enables us to create a minimized build of our entire
59892  * application using the Ext JS 4 SDK Tools.
59893  *
59894  * For more information about writing Ext JS 4 applications, please see the
59895  * [application architecture guide][mvc].
59896  *
59897  * [mvc]: #!/guide/application_architecture
59898  *
59899  * @docauthor Ed Spencer
59900  */
59901 Ext.define('Ext.app.Application', {
59902     extend: 'Ext.app.Controller',
59903
59904     requires: [
59905         'Ext.ModelManager',
59906         'Ext.data.Model',
59907         'Ext.data.StoreManager',
59908         'Ext.tip.QuickTipManager',
59909         'Ext.ComponentManager',
59910         'Ext.app.EventBus'
59911     ],
59912
59913     /**
59914      * @cfg {String} name The name of your application. This will also be the namespace for your views, controllers
59915      * models and stores. Don't use spaces or special characters in the name.
59916      */
59917
59918     /**
59919      * @cfg {Object} scope The scope to execute the {@link #launch} function in. Defaults to the Application
59920      * instance.
59921      */
59922     scope: undefined,
59923
59924     /**
59925      * @cfg {Boolean} enableQuickTips True to automatically set up Ext.tip.QuickTip support.
59926      */
59927     enableQuickTips: true,
59928
59929     /**
59930      * @cfg {String} defaultUrl When the app is first loaded, this url will be redirected to.
59931      */
59932
59933     /**
59934      * @cfg {String} appFolder The path to the directory which contains all application's classes.
59935      * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
59936      */
59937     appFolder: 'app',
59938
59939     /**
59940      * @cfg {Boolean} autoCreateViewport True to automatically load and instantiate AppName.view.Viewport
59941      * before firing the launch function.
59942      */
59943     autoCreateViewport: false,
59944
59945     /**
59946      * Creates new Application.
59947      * @param {Object} [config] Config object.
59948      */
59949     constructor: function(config) {
59950         config = config || {};
59951         Ext.apply(this, config);
59952
59953         var requires = config.requires || [];
59954
59955         Ext.Loader.setPath(this.name, this.appFolder);
59956
59957         if (this.paths) {
59958             Ext.Object.each(this.paths, function(key, value) {
59959                 Ext.Loader.setPath(key, value);
59960             });
59961         }
59962
59963         this.callParent(arguments);
59964
59965         this.eventbus = Ext.create('Ext.app.EventBus');
59966
59967         var controllers = Ext.Array.from(this.controllers),
59968             ln = controllers && controllers.length,
59969             i, controller;
59970
59971         this.controllers = Ext.create('Ext.util.MixedCollection');
59972
59973         if (this.autoCreateViewport) {
59974             requires.push(this.getModuleClassName('Viewport', 'view'));
59975         }
59976
59977         for (i = 0; i < ln; i++) {
59978             requires.push(this.getModuleClassName(controllers[i], 'controller'));
59979         }
59980
59981         Ext.require(requires);
59982
59983         Ext.onReady(function() {
59984             for (i = 0; i < ln; i++) {
59985                 controller = this.getController(controllers[i]);
59986                 controller.init(this);
59987             }
59988
59989             this.onBeforeLaunch.call(this);
59990         }, this);
59991     },
59992
59993     control: function(selectors, listeners, controller) {
59994         this.eventbus.control(selectors, listeners, controller);
59995     },
59996
59997     /**
59998      * Called automatically when the page has completely loaded. This is an empty function that should be
59999      * overridden by each application that needs to take action on page load
60000      * @property launch
60001      * @type Function
60002      * @param {String} profile The detected {@link #profiles application profile}
60003      * @return {Boolean} By default, the Application will dispatch to the configured startup controller and
60004      * action immediately after running the launch function. Return false to prevent this behavior.
60005      */
60006     launch: Ext.emptyFn,
60007
60008     /**
60009      * @private
60010      */
60011     onBeforeLaunch: function() {
60012         if (this.enableQuickTips) {
60013             Ext.tip.QuickTipManager.init();
60014         }
60015
60016         if (this.autoCreateViewport) {
60017             this.getView('Viewport').create();
60018         }
60019
60020         this.launch.call(this.scope || this);
60021         this.launched = true;
60022         this.fireEvent('launch', this);
60023
60024         this.controllers.each(function(controller) {
60025             controller.onLaunch(this);
60026         }, this);
60027     },
60028
60029     getModuleClassName: function(name, type) {
60030         var namespace = Ext.Loader.getPrefix(name);
60031
60032         if (namespace.length > 0 && namespace !== name) {
60033             return name;
60034         }
60035
60036         return this.name + '.' + type + '.' + name;
60037     },
60038
60039     getController: function(name) {
60040         var controller = this.controllers.get(name);
60041
60042         if (!controller) {
60043             controller = Ext.create(this.getModuleClassName(name, 'controller'), {
60044                 application: this,
60045                 id: name
60046             });
60047
60048             this.controllers.add(controller);
60049         }
60050
60051         return controller;
60052     },
60053
60054     getStore: function(name) {
60055         var store = Ext.StoreManager.get(name);
60056
60057         if (!store) {
60058             store = Ext.create(this.getModuleClassName(name, 'store'), {
60059                 storeId: name
60060             });
60061         }
60062
60063         return store;
60064     },
60065
60066     getModel: function(model) {
60067         model = this.getModuleClassName(model, 'model');
60068
60069         return Ext.ModelManager.getModel(model);
60070     },
60071
60072     getView: function(view) {
60073         view = this.getModuleClassName(view, 'view');
60074
60075         return Ext.ClassManager.get(view);
60076     }
60077 });
60078
60079 /**
60080  * @class Ext.chart.Callout
60081  * A mixin providing callout functionality for Ext.chart.series.Series.
60082  */
60083 Ext.define('Ext.chart.Callout', {
60084
60085     /* Begin Definitions */
60086
60087     /* End Definitions */
60088
60089     constructor: function(config) {
60090         if (config.callouts) {
60091             config.callouts.styles = Ext.applyIf(config.callouts.styles || {}, {
60092                 color: "#000",
60093                 font: "11px Helvetica, sans-serif"
60094             });
60095             this.callouts = Ext.apply(this.callouts || {}, config.callouts);
60096             this.calloutsArray = [];
60097         }
60098     },
60099
60100     renderCallouts: function() {
60101         if (!this.callouts) {
60102             return;
60103         }
60104
60105         var me = this,
60106             items = me.items,
60107             animate = me.chart.animate,
60108             config = me.callouts,
60109             styles = config.styles,
60110             group = me.calloutsArray,
60111             store = me.chart.store,
60112             len = store.getCount(),
60113             ratio = items.length / len,
60114             previouslyPlacedCallouts = [],
60115             i,
60116             count,
60117             j,
60118             p;
60119             
60120         for (i = 0, count = 0; i < len; i++) {
60121             for (j = 0; j < ratio; j++) {
60122                 var item = items[count],
60123                     label = group[count],
60124                     storeItem = store.getAt(i),
60125                     display;
60126                 
60127                 display = config.filter(storeItem);
60128                 
60129                 if (!display && !label) {
60130                     count++;
60131                     continue;               
60132                 }
60133                 
60134                 if (!label) {
60135                     group[count] = label = me.onCreateCallout(storeItem, item, i, display, j, count);
60136                 }
60137                 for (p in label) {
60138                     if (label[p] && label[p].setAttributes) {
60139                         label[p].setAttributes(styles, true);
60140                     }
60141                 }
60142                 if (!display) {
60143                     for (p in label) {
60144                         if (label[p]) {
60145                             if (label[p].setAttributes) {
60146                                 label[p].setAttributes({
60147                                     hidden: true
60148                                 }, true);
60149                             } else if(label[p].setVisible) {
60150                                 label[p].setVisible(false);
60151                             }
60152                         }
60153                     }
60154                 }
60155                 config.renderer(label, storeItem);
60156                 me.onPlaceCallout(label, storeItem, item, i, display, animate,
60157                                   j, count, previouslyPlacedCallouts);
60158                 previouslyPlacedCallouts.push(label);
60159                 count++;
60160             }
60161         }
60162         this.hideCallouts(count);
60163     },
60164
60165     onCreateCallout: function(storeItem, item, i, display) {
60166         var me = this,
60167             group = me.calloutsGroup,
60168             config = me.callouts,
60169             styles = config.styles,
60170             width = styles.width,
60171             height = styles.height,
60172             chart = me.chart,
60173             surface = chart.surface,
60174             calloutObj = {
60175                 //label: false,
60176                 //box: false,
60177                 lines: false
60178             };
60179
60180         calloutObj.lines = surface.add(Ext.apply({},
60181         {
60182             type: 'path',
60183             path: 'M0,0',
60184             stroke: me.getLegendColor() || '#555'
60185         },
60186         styles));
60187
60188         if (config.items) {
60189             calloutObj.panel = Ext.create('widget.panel', {
60190                 style: "position: absolute;",    
60191                 width: width,
60192                 height: height,
60193                 items: config.items,
60194                 renderTo: chart.el
60195             });
60196         }
60197
60198         return calloutObj;
60199     },
60200
60201     hideCallouts: function(index) {
60202         var calloutsArray = this.calloutsArray,
60203             len = calloutsArray.length,
60204             co,
60205             p;
60206         while (len-->index) {
60207             co = calloutsArray[len];
60208             for (p in co) {
60209                 if (co[p]) {
60210                     co[p].hide(true);
60211                 }
60212             }
60213         }
60214     }
60215 });
60216
60217 /**
60218  * @class Ext.draw.CompositeSprite
60219  * @extends Ext.util.MixedCollection
60220  *
60221  * A composite Sprite handles a group of sprites with common methods to a sprite
60222  * such as `hide`, `show`, `setAttributes`. These methods are applied to the set of sprites
60223  * added to the group.
60224  *
60225  * CompositeSprite extends {@link Ext.util.MixedCollection} so you can use the same methods
60226  * in `MixedCollection` to iterate through sprites, add and remove elements, etc.
60227  *
60228  * In order to create a CompositeSprite, one has to provide a handle to the surface where it is
60229  * rendered:
60230  *
60231  *     var group = Ext.create('Ext.draw.CompositeSprite', {
60232  *         surface: drawComponent.surface
60233  *     });
60234  *                  
60235  * Then just by using `MixedCollection` methods it's possible to add {@link Ext.draw.Sprite}s:
60236  *  
60237  *     group.add(sprite1);
60238  *     group.add(sprite2);
60239  *     group.add(sprite3);
60240  *                  
60241  * And then apply common Sprite methods to them:
60242  *  
60243  *     group.setAttributes({
60244  *         fill: '#f00'
60245  *     }, true);
60246  */
60247 Ext.define('Ext.draw.CompositeSprite', {
60248
60249     /* Begin Definitions */
60250
60251     extend: 'Ext.util.MixedCollection',
60252     mixins: {
60253         animate: 'Ext.util.Animate'
60254     },
60255
60256     /* End Definitions */
60257     isCompositeSprite: true,
60258     constructor: function(config) {
60259         var me = this;
60260         
60261         config = config || {};
60262         Ext.apply(me, config);
60263
60264         me.addEvents(
60265             'mousedown',
60266             'mouseup',
60267             'mouseover',
60268             'mouseout',
60269             'click'
60270         );
60271         me.id = Ext.id(null, 'ext-sprite-group-');
60272         me.callParent();
60273     },
60274
60275     // @private
60276     onClick: function(e) {
60277         this.fireEvent('click', e);
60278     },
60279
60280     // @private
60281     onMouseUp: function(e) {
60282         this.fireEvent('mouseup', e);
60283     },
60284
60285     // @private
60286     onMouseDown: function(e) {
60287         this.fireEvent('mousedown', e);
60288     },
60289
60290     // @private
60291     onMouseOver: function(e) {
60292         this.fireEvent('mouseover', e);
60293     },
60294
60295     // @private
60296     onMouseOut: function(e) {
60297         this.fireEvent('mouseout', e);
60298     },
60299
60300     attachEvents: function(o) {
60301         var me = this;
60302         
60303         o.on({
60304             scope: me,
60305             mousedown: me.onMouseDown,
60306             mouseup: me.onMouseUp,
60307             mouseover: me.onMouseOver,
60308             mouseout: me.onMouseOut,
60309             click: me.onClick
60310         });
60311     },
60312
60313     // Inherit docs from MixedCollection
60314     add: function(key, o) {
60315         var result = this.callParent(arguments);
60316         this.attachEvents(result);
60317         return result;
60318     },
60319
60320     insert: function(index, key, o) {
60321         return this.callParent(arguments);
60322     },
60323
60324     // Inherit docs from MixedCollection
60325     remove: function(o) {
60326         var me = this;
60327         
60328         o.un({
60329             scope: me,
60330             mousedown: me.onMouseDown,
60331             mouseup: me.onMouseUp,
60332             mouseover: me.onMouseOver,
60333             mouseout: me.onMouseOut,
60334             click: me.onClick
60335         });
60336         return me.callParent(arguments);
60337     },
60338     
60339     /**
60340      * Returns the group bounding box.
60341      * Behaves like {@link Ext.draw.Sprite#getBBox} method.
60342      * @return {Object} an object with x, y, width, and height properties.
60343      */
60344     getBBox: function() {
60345         var i = 0,
60346             sprite,
60347             bb,
60348             items = this.items,
60349             len = this.length,
60350             infinity = Infinity,
60351             minX = infinity,
60352             maxHeight = -infinity,
60353             minY = infinity,
60354             maxWidth = -infinity,
60355             maxWidthBBox, maxHeightBBox;
60356         
60357         for (; i < len; i++) {
60358             sprite = items[i];
60359             if (sprite.el) {
60360                 bb = sprite.getBBox();
60361                 minX = Math.min(minX, bb.x);
60362                 minY = Math.min(minY, bb.y);
60363                 maxHeight = Math.max(maxHeight, bb.height + bb.y);
60364                 maxWidth = Math.max(maxWidth, bb.width + bb.x);
60365             }
60366         }
60367         
60368         return {
60369             x: minX,
60370             y: minY,
60371             height: maxHeight - minY,
60372             width: maxWidth - minX
60373         };
60374     },
60375
60376     /**
60377      * Iterates through all sprites calling `setAttributes` on each one. For more information {@link Ext.draw.Sprite}
60378      * provides a description of the attributes that can be set with this method.
60379      * @param {Object} attrs Attributes to be changed on the sprite.
60380      * @param {Boolean} redraw Flag to immediatly draw the change.
60381      * @return {Ext.draw.CompositeSprite} this
60382      */
60383     setAttributes: function(attrs, redraw) {
60384         var i = 0,
60385             items = this.items,
60386             len = this.length;
60387             
60388         for (; i < len; i++) {
60389             items[i].setAttributes(attrs, redraw);
60390         }
60391         return this;
60392     },
60393
60394     /**
60395      * Hides all sprites. If the first parameter of the method is true
60396      * then a redraw will be forced for each sprite.
60397      * @param {Boolean} redraw Flag to immediatly draw the change.
60398      * @return {Ext.draw.CompositeSprite} this
60399      */
60400     hide: function(redraw) {
60401         var i = 0,
60402             items = this.items,
60403             len = this.length;
60404             
60405         for (; i < len; i++) {
60406             items[i].hide(redraw);
60407         }
60408         return this;
60409     },
60410
60411     /**
60412      * Shows all sprites. If the first parameter of the method is true
60413      * then a redraw will be forced for each sprite.
60414      * @param {Boolean} redraw Flag to immediatly draw the change.
60415      * @return {Ext.draw.CompositeSprite} this
60416      */
60417     show: function(redraw) {
60418         var i = 0,
60419             items = this.items,
60420             len = this.length;
60421             
60422         for (; i < len; i++) {
60423             items[i].show(redraw);
60424         }
60425         return this;
60426     },
60427
60428     redraw: function() {
60429         var me = this,
60430             i = 0,
60431             items = me.items,
60432             surface = me.getSurface(),
60433             len = me.length;
60434         
60435         if (surface) {
60436             for (; i < len; i++) {
60437                 surface.renderItem(items[i]);
60438             }
60439         }
60440         return me;
60441     },
60442
60443     setStyle: function(obj) {
60444         var i = 0,
60445             items = this.items,
60446             len = this.length,
60447             item, el;
60448             
60449         for (; i < len; i++) {
60450             item = items[i];
60451             el = item.el;
60452             if (el) {
60453                 el.setStyle(obj);
60454             }
60455         }
60456     },
60457
60458     addCls: function(obj) {
60459         var i = 0,
60460             items = this.items,
60461             surface = this.getSurface(),
60462             len = this.length;
60463         
60464         if (surface) {
60465             for (; i < len; i++) {
60466                 surface.addCls(items[i], obj);
60467             }
60468         }
60469     },
60470
60471     removeCls: function(obj) {
60472         var i = 0,
60473             items = this.items,
60474             surface = this.getSurface(),
60475             len = this.length;
60476         
60477         if (surface) {
60478             for (; i < len; i++) {
60479                 surface.removeCls(items[i], obj);
60480             }
60481         }
60482     },
60483     
60484     /**
60485      * Grab the surface from the items
60486      * @private
60487      * @return {Ext.draw.Surface} The surface, null if not found
60488      */
60489     getSurface: function(){
60490         var first = this.first();
60491         if (first) {
60492             return first.surface;
60493         }
60494         return null;
60495     },
60496     
60497     /**
60498      * Destroys the SpriteGroup
60499      */
60500     destroy: function(){
60501         var me = this,
60502             surface = me.getSurface(),
60503             item;
60504             
60505         if (surface) {
60506             while (me.getCount() > 0) {
60507                 item = me.first();
60508                 me.remove(item);
60509                 surface.remove(item);
60510             }
60511         }
60512         me.clearListeners();
60513     }
60514 });
60515
60516 /**
60517  * @class Ext.layout.component.Auto
60518  * @extends Ext.layout.component.Component
60519  * @private
60520  *
60521  * <p>The AutoLayout is the default layout manager delegated by {@link Ext.Component} to
60522  * render any child Elements when no <tt>{@link Ext.container.Container#layout layout}</tt> is configured.</p>
60523  */
60524
60525 Ext.define('Ext.layout.component.Auto', {
60526
60527     /* Begin Definitions */
60528
60529     alias: 'layout.autocomponent',
60530
60531     extend: 'Ext.layout.component.Component',
60532
60533     /* End Definitions */
60534
60535     type: 'autocomponent',
60536
60537     onLayout : function(width, height) {
60538         this.setTargetSize(width, height);
60539     }
60540 });
60541 /**
60542  * @class Ext.chart.theme.Theme
60543  * 
60544  * Provides chart theming.
60545  * 
60546  * Used as mixins by Ext.chart.Chart.
60547  */
60548 Ext.define('Ext.chart.theme.Theme', {
60549
60550     /* Begin Definitions */
60551
60552     requires: ['Ext.draw.Color'],
60553
60554     /* End Definitions */
60555
60556     theme: 'Base',
60557     themeAttrs: false,
60558     
60559     initTheme: function(theme) {
60560         var me = this,
60561             themes = Ext.chart.theme,
60562             key, gradients;
60563         if (theme) {
60564             theme = theme.split(':');
60565             for (key in themes) {
60566                 if (key == theme[0]) {
60567                     gradients = theme[1] == 'gradients';
60568                     me.themeAttrs = new themes[key]({
60569                         useGradients: gradients
60570                     });
60571                     if (gradients) {
60572                         me.gradients = me.themeAttrs.gradients;
60573                     }
60574                     if (me.themeAttrs.background) {
60575                         me.background = me.themeAttrs.background;
60576                     }
60577                     return;
60578                 }
60579             }
60580             Ext.Error.raise('No theme found named "' + theme + '"');
60581         }
60582     }
60583 }, 
60584 // This callback is executed right after when the class is created. This scope refers to the newly created class itself
60585 function() {
60586    /* Theme constructor: takes either a complex object with styles like:
60587   
60588    {
60589         axis: {
60590             fill: '#000',
60591             'stroke-width': 1
60592         },
60593         axisLabelTop: {
60594             fill: '#000',
60595             font: '11px Arial'
60596         },
60597         axisLabelLeft: {
60598             fill: '#000',
60599             font: '11px Arial'
60600         },
60601         axisLabelRight: {
60602             fill: '#000',
60603             font: '11px Arial'
60604         },
60605         axisLabelBottom: {
60606             fill: '#000',
60607             font: '11px Arial'
60608         },
60609         axisTitleTop: {
60610             fill: '#000',
60611             font: '11px Arial'
60612         },
60613         axisTitleLeft: {
60614             fill: '#000',
60615             font: '11px Arial'
60616         },
60617         axisTitleRight: {
60618             fill: '#000',
60619             font: '11px Arial'
60620         },
60621         axisTitleBottom: {
60622             fill: '#000',
60623             font: '11px Arial'
60624         },
60625         series: {
60626             'stroke-width': 1
60627         },
60628         seriesLabel: {
60629             font: '12px Arial',
60630             fill: '#333'
60631         },
60632         marker: {
60633             stroke: '#555',
60634             fill: '#000',
60635             radius: 3,
60636             size: 3
60637         },
60638         seriesThemes: [{
60639             fill: '#C6DBEF'
60640         }, {
60641             fill: '#9ECAE1'
60642         }, {
60643             fill: '#6BAED6'
60644         }, {
60645             fill: '#4292C6'
60646         }, {
60647             fill: '#2171B5'
60648         }, {
60649             fill: '#084594'
60650         }],
60651         markerThemes: [{
60652             fill: '#084594',
60653             type: 'circle' 
60654         }, {
60655             fill: '#2171B5',
60656             type: 'cross'
60657         }, {
60658             fill: '#4292C6',
60659             type: 'plus'
60660         }]
60661     }
60662   
60663   ...or also takes just an array of colors and creates the complex object:
60664   
60665   {
60666       colors: ['#aaa', '#bcd', '#eee']
60667   }
60668   
60669   ...or takes just a base color and makes a theme from it
60670   
60671   {
60672       baseColor: '#bce'
60673   }
60674   
60675   To create a new theme you may add it to the Themes object:
60676   
60677   Ext.chart.theme.MyNewTheme = Ext.extend(Object, {
60678       constructor: function(config) {
60679           Ext.chart.theme.call(this, config, {
60680               baseColor: '#mybasecolor'
60681           });
60682       }
60683   });
60684   
60685   //Proposal:
60686   Ext.chart.theme.MyNewTheme = Ext.chart.createTheme('#basecolor');
60687   
60688   ...and then to use it provide the name of the theme (as a lower case string) in the chart config.
60689   
60690   {
60691       theme: 'mynewtheme'
60692   }
60693  */
60694
60695 (function() {
60696     Ext.chart.theme = function(config, base) {
60697         config = config || {};
60698         var i = 0, l, colors, color,
60699             seriesThemes, markerThemes,
60700             seriesTheme, markerTheme, 
60701             key, gradients = [],
60702             midColor, midL;
60703         
60704         if (config.baseColor) {
60705             midColor = Ext.draw.Color.fromString(config.baseColor);
60706             midL = midColor.getHSL()[2];
60707             if (midL < 0.15) {
60708                 midColor = midColor.getLighter(0.3);
60709             } else if (midL < 0.3) {
60710                 midColor = midColor.getLighter(0.15);
60711             } else if (midL > 0.85) {
60712                 midColor = midColor.getDarker(0.3);
60713             } else if (midL > 0.7) {
60714                 midColor = midColor.getDarker(0.15);
60715             }
60716             config.colors = [ midColor.getDarker(0.3).toString(),
60717                               midColor.getDarker(0.15).toString(),
60718                               midColor.toString(),
60719                               midColor.getLighter(0.15).toString(),
60720                               midColor.getLighter(0.3).toString()];
60721
60722             delete config.baseColor;
60723         }
60724         if (config.colors) {
60725             colors = config.colors.slice();
60726             markerThemes = base.markerThemes;
60727             seriesThemes = base.seriesThemes;
60728             l = colors.length;
60729             base.colors = colors;
60730             for (; i < l; i++) {
60731                 color = colors[i];
60732                 markerTheme = markerThemes[i] || {};
60733                 seriesTheme = seriesThemes[i] || {};
60734                 markerTheme.fill = seriesTheme.fill = markerTheme.stroke = seriesTheme.stroke = color;
60735                 markerThemes[i] = markerTheme;
60736                 seriesThemes[i] = seriesTheme;
60737             }
60738             base.markerThemes = markerThemes.slice(0, l);
60739             base.seriesThemes = seriesThemes.slice(0, l);
60740         //the user is configuring something in particular (either markers, series or pie slices)
60741         }
60742         for (key in base) {
60743             if (key in config) {
60744                 if (Ext.isObject(config[key]) && Ext.isObject(base[key])) {
60745                     Ext.apply(base[key], config[key]);
60746                 } else {
60747                     base[key] = config[key];
60748                 }
60749             }
60750         }
60751         if (config.useGradients) {
60752             colors = base.colors || (function () {
60753                 var ans = [];
60754                 for (i = 0, seriesThemes = base.seriesThemes, l = seriesThemes.length; i < l; i++) {
60755                     ans.push(seriesThemes[i].fill || seriesThemes[i].stroke);
60756                 }
60757                 return ans;
60758             })();
60759             for (i = 0, l = colors.length; i < l; i++) {
60760                 midColor = Ext.draw.Color.fromString(colors[i]);
60761                 if (midColor) {
60762                     color = midColor.getDarker(0.1).toString();
60763                     midColor = midColor.toString();
60764                     key = 'theme-' + midColor.substr(1) + '-' + color.substr(1);
60765                     gradients.push({
60766                         id: key,
60767                         angle: 45,
60768                         stops: {
60769                             0: {
60770                                 color: midColor.toString()
60771                             },
60772                             100: {
60773                                 color: color.toString()
60774                             }
60775                         }
60776                     });
60777                     colors[i] = 'url(#' + key + ')'; 
60778                 }
60779             }
60780             base.gradients = gradients;
60781             base.colors = colors;
60782         }
60783         /*
60784         base.axis = Ext.apply(base.axis || {}, config.axis || {});
60785         base.axisLabel = Ext.apply(base.axisLabel || {}, config.axisLabel || {});
60786         base.axisTitle = Ext.apply(base.axisTitle || {}, config.axisTitle || {});
60787         */
60788         Ext.apply(this, base);
60789     };
60790 })();
60791 });
60792
60793 /**
60794  * @class Ext.chart.Mask
60795  *
60796  * Defines a mask for a chart's series.
60797  * The 'chart' member must be set prior to rendering.
60798  *
60799  * A Mask can be used to select a certain region in a chart.
60800  * When enabled, the `select` event will be triggered when a
60801  * region is selected by the mask, allowing the user to perform
60802  * other tasks like zooming on that region, etc.
60803  *
60804  * In order to use the mask one has to set the Chart `mask` option to
60805  * `true`, `vertical` or `horizontal`. Then a possible configuration for the
60806  * listener could be:
60807  *
60808         items: {
60809             xtype: 'chart',
60810             animate: true,
60811             store: store1,
60812             mask: 'horizontal',
60813             listeners: {
60814                 select: {
60815                     fn: function(me, selection) {
60816                         me.setZoom(selection);
60817                         me.mask.hide();
60818                     }
60819                 }
60820             },
60821
60822  * In this example we zoom the chart to that particular region. You can also get
60823  * a handle to a mask instance from the chart object. The `chart.mask` element is a
60824  * `Ext.Panel`.
60825  * 
60826  */
60827 Ext.define('Ext.chart.Mask', {
60828     require: ['Ext.chart.MaskLayer'],
60829     /**
60830      * Creates new Mask.
60831      * @param {Object} config (optional) Config object.
60832      */
60833     constructor: function(config) {
60834         var me = this;
60835
60836         me.addEvents('select');
60837
60838         if (config) {
60839             Ext.apply(me, config);
60840         }
60841         if (me.mask) {
60842             me.on('afterrender', function() {
60843                 //create a mask layer component
60844                 var comp = Ext.create('Ext.chart.MaskLayer', {
60845                     renderTo: me.el
60846                 });
60847                 comp.el.on({
60848                     'mousemove': function(e) {
60849                         me.onMouseMove(e);
60850                     },
60851                     'mouseup': function(e) {
60852                         me.resized(e);
60853                     }
60854                 });
60855                 //create a resize handler for the component
60856                 var resizeHandler = Ext.create('Ext.resizer.Resizer', {
60857                     el: comp.el,
60858                     handles: 'all',
60859                     pinned: true
60860                 });
60861                 resizeHandler.on({
60862                     'resize': function(e) {
60863                         me.resized(e);    
60864                     }    
60865                 });
60866                 comp.initDraggable();
60867                 me.maskType = me.mask;
60868                 me.mask = comp;
60869                 me.maskSprite = me.surface.add({
60870                     type: 'path',
60871                     path: ['M', 0, 0],
60872                     zIndex: 1001,
60873                     opacity: 0.7,
60874                     hidden: true,
60875                     stroke: '#444'
60876                 });
60877             }, me, { single: true });
60878         }
60879     },
60880     
60881     resized: function(e) {
60882         var me = this,
60883             bbox = me.bbox || me.chartBBox,
60884             x = bbox.x,
60885             y = bbox.y,
60886             width = bbox.width,
60887             height = bbox.height,
60888             box = me.mask.getBox(true),
60889             max = Math.max,
60890             min = Math.min,
60891             staticX = box.x - x,
60892             staticY = box.y - y;
60893         
60894         staticX = max(staticX, x);
60895         staticY = max(staticY, y);
60896         staticX = min(staticX, width);
60897         staticY = min(staticY, height);
60898         box.x = staticX;
60899         box.y = staticY;
60900         me.fireEvent('select', me, box);
60901     },
60902
60903     onMouseUp: function(e) {
60904         var me = this,
60905             bbox = me.bbox || me.chartBBox,
60906             sel = me.maskSelection;
60907         me.maskMouseDown = false;
60908         me.mouseDown = false;
60909         if (me.mouseMoved) {
60910             me.onMouseMove(e);
60911             me.mouseMoved = false;
60912             me.fireEvent('select', me, {
60913                 x: sel.x - bbox.x,
60914                 y: sel.y - bbox.y,
60915                 width: sel.width,
60916                 height: sel.height
60917             });
60918         }
60919     },
60920
60921     onMouseDown: function(e) {
60922         var me = this;
60923         me.mouseDown = true;
60924         me.mouseMoved = false;
60925         me.maskMouseDown = {
60926             x: e.getPageX() - me.el.getX(),
60927             y: e.getPageY() - me.el.getY()
60928         };
60929     },
60930
60931     onMouseMove: function(e) {
60932         var me = this,
60933             mask = me.maskType,
60934             bbox = me.bbox || me.chartBBox,
60935             x = bbox.x,
60936             y = bbox.y,
60937             math = Math,
60938             floor = math.floor,
60939             abs = math.abs,
60940             min = math.min,
60941             max = math.max,
60942             height = floor(y + bbox.height),
60943             width = floor(x + bbox.width),
60944             posX = e.getPageX(),
60945             posY = e.getPageY(),
60946             staticX = posX - me.el.getX(),
60947             staticY = posY - me.el.getY(),
60948             maskMouseDown = me.maskMouseDown,
60949             path;
60950         
60951         me.mouseMoved = me.mouseDown;
60952         staticX = max(staticX, x);
60953         staticY = max(staticY, y);
60954         staticX = min(staticX, width);
60955         staticY = min(staticY, height);
60956         if (maskMouseDown && me.mouseDown) {
60957             if (mask == 'horizontal') {
60958                 staticY = y;
60959                 maskMouseDown.y = height;
60960                 posY = me.el.getY() + bbox.height + me.insetPadding;
60961             }
60962             else if (mask == 'vertical') {
60963                 staticX = x;
60964                 maskMouseDown.x = width;
60965             }
60966             width = maskMouseDown.x - staticX;
60967             height = maskMouseDown.y - staticY;
60968             path = ['M', staticX, staticY, 'l', width, 0, 0, height, -width, 0, 'z'];
60969             me.maskSelection = {
60970                 x: width > 0 ? staticX : staticX + width,
60971                 y: height > 0 ? staticY : staticY + height,
60972                 width: abs(width),
60973                 height: abs(height)
60974             };
60975             me.mask.updateBox(me.maskSelection);
60976             me.mask.show();
60977             me.maskSprite.setAttributes({
60978                 hidden: true    
60979             }, true);
60980         }
60981         else {
60982             if (mask == 'horizontal') {
60983                 path = ['M', staticX, y, 'L', staticX, height];
60984             }
60985             else if (mask == 'vertical') {
60986                 path = ['M', x, staticY, 'L', width, staticY];
60987             }
60988             else {
60989                 path = ['M', staticX, y, 'L', staticX, height, 'M', x, staticY, 'L', width, staticY];
60990             }
60991             me.maskSprite.setAttributes({
60992                 path: path,
60993                 fill: me.maskMouseDown ? me.maskSprite.stroke : false,
60994                 'stroke-width': mask === true ? 1 : 3,
60995                 hidden: false
60996             }, true);
60997         }
60998     },
60999
61000     onMouseLeave: function(e) {
61001         var me = this;
61002         me.mouseMoved = false;
61003         me.mouseDown = false;
61004         me.maskMouseDown = false;
61005         me.mask.hide();
61006         me.maskSprite.hide(true);
61007     }
61008 });
61009     
61010 /**
61011  * @class Ext.chart.Navigation
61012  *
61013  * Handles panning and zooming capabilities.
61014  *
61015  * Used as mixin by Ext.chart.Chart.
61016  */
61017 Ext.define('Ext.chart.Navigation', {
61018
61019     constructor: function() {
61020         this.originalStore = this.store;
61021     },
61022
61023     /**
61024      * Zooms the chart to the specified selection range.
61025      * Can be used with a selection mask. For example:
61026      *
61027      *     items: {
61028      *         xtype: 'chart',
61029      *         animate: true,
61030      *         store: store1,
61031      *         mask: 'horizontal',
61032      *         listeners: {
61033      *             select: {
61034      *                 fn: function(me, selection) {
61035      *                     me.setZoom(selection);
61036      *                     me.mask.hide();
61037      *                 }
61038      *             }
61039      *         }
61040      *     }
61041      */
61042     setZoom: function(zoomConfig) {
61043         var me = this,
61044             axes = me.axes,
61045             bbox = me.chartBBox,
61046             xScale = 1 / bbox.width,
61047             yScale = 1 / bbox.height,
61048             zoomer = {
61049                 x : zoomConfig.x * xScale,
61050                 y : zoomConfig.y * yScale,
61051                 width : zoomConfig.width * xScale,
61052                 height : zoomConfig.height * yScale
61053             };
61054         axes.each(function(axis) {
61055             var ends = axis.calcEnds();
61056             if (axis.position == 'bottom' || axis.position == 'top') {
61057                 var from = (ends.to - ends.from) * zoomer.x + ends.from,
61058                     to = (ends.to - ends.from) * zoomer.width + from;
61059                 axis.minimum = from;
61060                 axis.maximum = to;
61061             } else {
61062                 var to = (ends.to - ends.from) * (1 - zoomer.y) + ends.from,
61063                     from = to - (ends.to - ends.from) * zoomer.height;
61064                 axis.minimum = from;
61065                 axis.maximum = to;
61066             }
61067         });
61068         me.redraw(false);
61069     },
61070
61071     /**
61072      * Restores the zoom to the original value. This can be used to reset
61073      * the previous zoom state set by `setZoom`. For example:
61074      *
61075      *     myChart.restoreZoom();
61076      */
61077     restoreZoom: function() {
61078         this.store = this.substore = this.originalStore;
61079         this.redraw(true);
61080     }
61081
61082 });
61083
61084 /**
61085  * @class Ext.chart.Shape
61086  * @ignore
61087  */
61088 Ext.define('Ext.chart.Shape', {
61089
61090     /* Begin Definitions */
61091
61092     singleton: true,
61093
61094     /* End Definitions */
61095
61096     circle: function (surface, opts) {
61097         return surface.add(Ext.apply({
61098             type: 'circle',
61099             x: opts.x,
61100             y: opts.y,
61101             stroke: null,
61102             radius: opts.radius
61103         }, opts));
61104     },
61105     line: function (surface, opts) {
61106         return surface.add(Ext.apply({
61107             type: 'rect',
61108             x: opts.x - opts.radius,
61109             y: opts.y - opts.radius,
61110             height: 2 * opts.radius,
61111             width: 2 * opts.radius / 5
61112         }, opts));
61113     },
61114     square: function (surface, opts) {
61115         return surface.add(Ext.applyIf({
61116             type: 'rect',
61117             x: opts.x - opts.radius,
61118             y: opts.y - opts.radius,
61119             height: 2 * opts.radius,
61120             width: 2 * opts.radius,
61121             radius: null
61122         }, opts));
61123     },
61124     triangle: function (surface, opts) {
61125         opts.radius *= 1.75;
61126         return surface.add(Ext.apply({
61127             type: 'path',
61128             stroke: null,
61129             path: "M".concat(opts.x, ",", opts.y, "m0-", opts.radius * 0.58, "l", opts.radius * 0.5, ",", opts.radius * 0.87, "-", opts.radius, ",0z")
61130         }, opts));
61131     },
61132     diamond: function (surface, opts) {
61133         var r = opts.radius;
61134         r *= 1.5;
61135         return surface.add(Ext.apply({
61136             type: 'path',
61137             stroke: null,
61138             path: ["M", opts.x, opts.y - r, "l", r, r, -r, r, -r, -r, r, -r, "z"]
61139         }, opts));
61140     },
61141     cross: function (surface, opts) {
61142         var r = opts.radius;
61143         r = r / 1.7;
61144         return surface.add(Ext.apply({
61145             type: 'path',
61146             stroke: null,
61147             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"])
61148         }, opts));
61149     },
61150     plus: function (surface, opts) {
61151         var r = opts.radius / 1.3;
61152         return surface.add(Ext.apply({
61153             type: 'path',
61154             stroke: null,
61155             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"])
61156         }, opts));
61157     },
61158     arrow: function (surface, opts) {
61159         var r = opts.radius;
61160         return surface.add(Ext.apply({
61161             type: 'path',
61162             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")
61163         }, opts));
61164     },
61165     drop: function (surface, x, y, text, size, angle) {
61166         size = size || 30;
61167         angle = angle || 0;
61168         surface.add({
61169             type: 'path',
61170             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'],
61171             fill: '#000',
61172             stroke: 'none',
61173             rotate: {
61174                 degrees: 22.5 - angle,
61175                 x: x,
61176                 y: y
61177             }
61178         });
61179         angle = (angle + 90) * Math.PI / 180;
61180         surface.add({
61181             type: 'text',
61182             x: x + size * Math.sin(angle) - 10, // Shift here, Not sure why.
61183             y: y + size * Math.cos(angle) + 5,
61184             text:  text,
61185             'font-size': size * 12 / 40,
61186             stroke: 'none',
61187             fill: '#fff'
61188         });
61189     }
61190 });
61191 /**
61192  * A Surface is an interface to render methods inside a draw {@link Ext.draw.Component}.
61193  * A Surface contains methods to render sprites, get bounding boxes of sprites, add
61194  * sprites to the canvas, initialize other graphic components, etc. One of the most used
61195  * methods for this class is the `add` method, to add Sprites to the surface.
61196  *
61197  * Most of the Surface methods are abstract and they have a concrete implementation
61198  * in VML or SVG engines.
61199  *
61200  * A Surface instance can be accessed as a property of a draw component. For example:
61201  *
61202  *     drawComponent.surface.add({
61203  *         type: 'circle',
61204  *         fill: '#ffc',
61205  *         radius: 100,
61206  *         x: 100,
61207  *         y: 100
61208  *     });
61209  *
61210  * The configuration object passed in the `add` method is the same as described in the {@link Ext.draw.Sprite}
61211  * class documentation.
61212  *
61213  * # Listeners
61214  *
61215  * You can also add event listeners to the surface using the `Observable` listener syntax. Supported events are:
61216  *
61217  * - mousedown
61218  * - mouseup
61219  * - mouseover
61220  * - mouseout
61221  * - mousemove
61222  * - mouseenter
61223  * - mouseleave
61224  * - click
61225  *
61226  * For example:
61227  *
61228  *     drawComponent.surface.on({
61229  *        'mousemove': function() {
61230  *             console.log('moving the mouse over the surface');
61231  *         }
61232  *     });
61233  *
61234  * # Example
61235  *
61236  *     var drawComponent = Ext.create('Ext.draw.Component', {
61237  *         width: 800,
61238  *         height: 600,
61239  *         renderTo: document.body
61240  *     }), surface = drawComponent.surface;
61241  *
61242  *     surface.add([{
61243  *         type: 'circle',
61244  *         radius: 10,
61245  *         fill: '#f00',
61246  *         x: 10,
61247  *         y: 10,
61248  *         group: 'circles'
61249  *     }, {
61250  *         type: 'circle',
61251  *         radius: 10,
61252  *         fill: '#0f0',
61253  *         x: 50,
61254  *         y: 50,
61255  *         group: 'circles'
61256  *     }, {
61257  *         type: 'circle',
61258  *         radius: 10,
61259  *         fill: '#00f',
61260  *         x: 100,
61261  *         y: 100,
61262  *         group: 'circles'
61263  *     }, {
61264  *         type: 'rect',
61265  *         width: 20,
61266  *         height: 20,
61267  *         fill: '#f00',
61268  *         x: 10,
61269  *         y: 10,
61270  *         group: 'rectangles'
61271  *     }, {
61272  *         type: 'rect',
61273  *         width: 20,
61274  *         height: 20,
61275  *         fill: '#0f0',
61276  *         x: 50,
61277  *         y: 50,
61278  *         group: 'rectangles'
61279  *     }, {
61280  *         type: 'rect',
61281  *         width: 20,
61282  *         height: 20,
61283  *         fill: '#00f',
61284  *         x: 100,
61285  *         y: 100,
61286  *         group: 'rectangles'
61287  *     }]);
61288  *
61289  *     // Get references to my groups
61290  *     circles = surface.getGroup('circles');
61291  *     rectangles = surface.getGroup('rectangles');
61292  *
61293  *     // Animate the circles down
61294  *     circles.animate({
61295  *         duration: 1000,
61296  *         to: {
61297  *             translate: {
61298  *                 y: 200
61299  *             }
61300  *         }
61301  *     });
61302  *
61303  *     // Animate the rectangles across
61304  *     rectangles.animate({
61305  *         duration: 1000,
61306  *         to: {
61307  *             translate: {
61308  *                 x: 200
61309  *             }
61310  *         }
61311  *     });
61312  */
61313 Ext.define('Ext.draw.Surface', {
61314
61315     /* Begin Definitions */
61316
61317     mixins: {
61318         observable: 'Ext.util.Observable'
61319     },
61320
61321     requires: ['Ext.draw.CompositeSprite'],
61322     uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml'],
61323
61324     separatorRe: /[, ]+/,
61325
61326     statics: {
61327         /**
61328          * Creates and returns a new concrete Surface instance appropriate for the current environment.
61329          * @param {Object} config Initial configuration for the Surface instance
61330          * @param {String[]} enginePriority (Optional) order of implementations to use; the first one that is
61331          * available in the current environment will be used. Defaults to `['Svg', 'Vml']`.
61332          * @return {Object} The created Surface or false.
61333          * @static
61334          */
61335         create: function(config, enginePriority) {
61336             enginePriority = enginePriority || ['Svg', 'Vml'];
61337
61338             var i = 0,
61339                 len = enginePriority.length,
61340                 surfaceClass;
61341
61342             for (; i < len; i++) {
61343                 if (Ext.supports[enginePriority[i]]) {
61344                     return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
61345                 }
61346             }
61347             return false;
61348         }
61349     },
61350
61351     /* End Definitions */
61352
61353     // @private
61354     availableAttrs: {
61355         blur: 0,
61356         "clip-rect": "0 0 1e9 1e9",
61357         cursor: "default",
61358         cx: 0,
61359         cy: 0,
61360         'dominant-baseline': 'auto',
61361         fill: "none",
61362         "fill-opacity": 1,
61363         font: '10px "Arial"',
61364         "font-family": '"Arial"',
61365         "font-size": "10",
61366         "font-style": "normal",
61367         "font-weight": 400,
61368         gradient: "",
61369         height: 0,
61370         hidden: false,
61371         href: "http://sencha.com/",
61372         opacity: 1,
61373         path: "M0,0",
61374         radius: 0,
61375         rx: 0,
61376         ry: 0,
61377         scale: "1 1",
61378         src: "",
61379         stroke: "#000",
61380         "stroke-dasharray": "",
61381         "stroke-linecap": "butt",
61382         "stroke-linejoin": "butt",
61383         "stroke-miterlimit": 0,
61384         "stroke-opacity": 1,
61385         "stroke-width": 1,
61386         target: "_blank",
61387         text: "",
61388         "text-anchor": "middle",
61389         title: "Ext Draw",
61390         width: 0,
61391         x: 0,
61392         y: 0,
61393         zIndex: 0
61394     },
61395
61396     /**
61397      * @cfg {Number} height
61398      * The height of this component in pixels (defaults to auto).
61399      */
61400     /**
61401      * @cfg {Number} width
61402      * The width of this component in pixels (defaults to auto).
61403      */
61404
61405     container: undefined,
61406     height: 352,
61407     width: 512,
61408     x: 0,
61409     y: 0,
61410
61411     /**
61412      * @private Flag indicating that the surface implementation requires sprites to be maintained
61413      * in order of their zIndex. Impls that don't require this can set it to false.
61414      */
61415     orderSpritesByZIndex: true,
61416
61417
61418     /**
61419      * Creates new Surface.
61420      * @param {Object} config (optional) Config object.
61421      */
61422     constructor: function(config) {
61423         var me = this;
61424         config = config || {};
61425         Ext.apply(me, config);
61426
61427         me.domRef = Ext.getDoc().dom;
61428
61429         me.customAttributes = {};
61430
61431         me.addEvents(
61432             'mousedown',
61433             'mouseup',
61434             'mouseover',
61435             'mouseout',
61436             'mousemove',
61437             'mouseenter',
61438             'mouseleave',
61439             'click'
61440         );
61441
61442         me.mixins.observable.constructor.call(me);
61443
61444         me.getId();
61445         me.initGradients();
61446         me.initItems();
61447         if (me.renderTo) {
61448             me.render(me.renderTo);
61449             delete me.renderTo;
61450         }
61451         me.initBackground(config.background);
61452     },
61453
61454     // @private called to initialize components in the surface
61455     // this is dependent on the underlying implementation.
61456     initSurface: Ext.emptyFn,
61457
61458     // @private called to setup the surface to render an item
61459     //this is dependent on the underlying implementation.
61460     renderItem: Ext.emptyFn,
61461
61462     // @private
61463     renderItems: Ext.emptyFn,
61464
61465     // @private
61466     setViewBox: function (x, y, width, height) {
61467         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
61468             this.viewBox = {x: x, y: y, width: width, height: height};
61469             this.applyViewBox();
61470         }
61471     },
61472
61473     /**
61474      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
61475      *
61476      * For example:
61477      *
61478      *     drawComponent.surface.addCls(sprite, 'x-visible');
61479      *
61480      * @param {Object} sprite The sprite to add the class to.
61481      * @param {String/String[]} className The CSS class to add, or an array of classes
61482      * @method
61483      */
61484     addCls: Ext.emptyFn,
61485
61486     /**
61487      * Removes one or more CSS classes from the element.
61488      *
61489      * For example:
61490      *
61491      *     drawComponent.surface.removeCls(sprite, 'x-visible');
61492      *
61493      * @param {Object} sprite The sprite to remove the class from.
61494      * @param {String/String[]} className The CSS class to remove, or an array of classes
61495      * @method
61496      */
61497     removeCls: Ext.emptyFn,
61498
61499     /**
61500      * Sets CSS style attributes to an element.
61501      *
61502      * For example:
61503      *
61504      *     drawComponent.surface.setStyle(sprite, {
61505      *         'cursor': 'pointer'
61506      *     });
61507      *
61508      * @param {Object} sprite The sprite to add, or an array of classes to
61509      * @param {Object} styles An Object with CSS styles.
61510      * @method
61511      */
61512     setStyle: Ext.emptyFn,
61513
61514     // @private
61515     initGradients: function() {
61516         var gradients = this.gradients;
61517         if (gradients) {
61518             Ext.each(gradients, this.addGradient, this);
61519         }
61520     },
61521
61522     // @private
61523     initItems: function() {
61524         var items = this.items;
61525         this.items = Ext.create('Ext.draw.CompositeSprite');
61526         this.groups = Ext.create('Ext.draw.CompositeSprite');
61527         if (items) {
61528             this.add(items);
61529         }
61530     },
61531
61532     // @private
61533     initBackground: function(config) {
61534         var me = this,
61535             width = me.width,
61536             height = me.height,
61537             gradientId, gradient, backgroundSprite;
61538         if (config) {
61539             if (config.gradient) {
61540                 gradient = config.gradient;
61541                 gradientId = gradient.id;
61542                 me.addGradient(gradient);
61543                 me.background = me.add({
61544                     type: 'rect',
61545                     x: 0,
61546                     y: 0,
61547                     width: width,
61548                     height: height,
61549                     fill: 'url(#' + gradientId + ')'
61550                 });
61551             } else if (config.fill) {
61552                 me.background = me.add({
61553                     type: 'rect',
61554                     x: 0,
61555                     y: 0,
61556                     width: width,
61557                     height: height,
61558                     fill: config.fill
61559                 });
61560             } else if (config.image) {
61561                 me.background = me.add({
61562                     type: 'image',
61563                     x: 0,
61564                     y: 0,
61565                     width: width,
61566                     height: height,
61567                     src: config.image
61568                 });
61569             }
61570         }
61571     },
61572
61573     /**
61574      * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
61575      *
61576      * For example:
61577      *
61578      *     drawComponent.surface.setSize(500, 500);
61579      *
61580      * This method is generally called when also setting the size of the draw Component.
61581      *
61582      * @param {Number} w The new width of the canvas.
61583      * @param {Number} h The new height of the canvas.
61584      */
61585     setSize: function(w, h) {
61586         if (this.background) {
61587             this.background.setAttributes({
61588                 width: w,
61589                 height: h,
61590                 hidden: false
61591             }, true);
61592         }
61593         this.applyViewBox();
61594     },
61595
61596     // @private
61597     scrubAttrs: function(sprite) {
61598         var i,
61599             attrs = {},
61600             exclude = {},
61601             sattr = sprite.attr;
61602         for (i in sattr) {
61603             // Narrow down attributes to the main set
61604             if (this.translateAttrs.hasOwnProperty(i)) {
61605                 // Translated attr
61606                 attrs[this.translateAttrs[i]] = sattr[i];
61607                 exclude[this.translateAttrs[i]] = true;
61608             }
61609             else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) {
61610                 // Passtrhough attr
61611                 attrs[i] = sattr[i];
61612             }
61613         }
61614         return attrs;
61615     },
61616
61617     // @private
61618     onClick: function(e) {
61619         this.processEvent('click', e);
61620     },
61621
61622     // @private
61623     onMouseUp: function(e) {
61624         this.processEvent('mouseup', e);
61625     },
61626
61627     // @private
61628     onMouseDown: function(e) {
61629         this.processEvent('mousedown', e);
61630     },
61631
61632     // @private
61633     onMouseOver: function(e) {
61634         this.processEvent('mouseover', e);
61635     },
61636
61637     // @private
61638     onMouseOut: function(e) {
61639         this.processEvent('mouseout', e);
61640     },
61641
61642     // @private
61643     onMouseMove: function(e) {
61644         this.fireEvent('mousemove', e);
61645     },
61646
61647     // @private
61648     onMouseEnter: Ext.emptyFn,
61649
61650     // @private
61651     onMouseLeave: Ext.emptyFn,
61652
61653     /**
61654      * Adds a gradient definition to the Surface. Note that in some surface engines, adding
61655      * a gradient via this method will not take effect if the surface has already been rendered.
61656      * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
61657      * than calling this method, especially if the surface is rendered immediately (e.g. due to
61658      * 'renderTo' in its config). For more information on how to create gradients in the Chart
61659      * configuration object please refer to {@link Ext.chart.Chart}.
61660      *
61661      * The gradient object to be passed into this method is composed by:
61662      *
61663      * - **id** - string - The unique name of the gradient.
61664      * - **angle** - number, optional - The angle of the gradient in degrees.
61665      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
61666      *
61667      * For example:
61668      *
61669      *    drawComponent.surface.addGradient({
61670      *        id: 'gradientId',
61671      *        angle: 45,
61672      *        stops: {
61673      *            0: {
61674      *                color: '#555'
61675      *            },
61676      *            100: {
61677      *                color: '#ddd'
61678      *            }
61679      *        }
61680      *    });
61681      *
61682      * @method
61683      */
61684     addGradient: Ext.emptyFn,
61685
61686     /**
61687      * Adds a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be
61688      * passed into this method.
61689      *
61690      * For example:
61691      *
61692      *     drawComponent.surface.add({
61693      *         type: 'circle',
61694      *         fill: '#ffc',
61695      *         radius: 100,
61696      *         x: 100,
61697      *         y: 100
61698      *     });
61699      *
61700      */
61701     add: function() {
61702         var args = Array.prototype.slice.call(arguments),
61703             sprite,
61704             index;
61705
61706         var hasMultipleArgs = args.length > 1;
61707         if (hasMultipleArgs || Ext.isArray(args[0])) {
61708             var items = hasMultipleArgs ? args : args[0],
61709                 results = [],
61710                 i, ln, item;
61711
61712             for (i = 0, ln = items.length; i < ln; i++) {
61713                 item = items[i];
61714                 item = this.add(item);
61715                 results.push(item);
61716             }
61717
61718             return results;
61719         }
61720         sprite = this.prepareItems(args[0], true)[0];
61721         this.insertByZIndex(sprite);
61722         this.onAdd(sprite);
61723         return sprite;
61724     },
61725
61726     /**
61727      * @private
61728      * Inserts a given sprite into the correct position in the items collection, according to
61729      * its zIndex. It will be inserted at the end of an existing series of sprites with the same or
61730      * lower zIndex. By ensuring sprites are always ordered, this allows surface subclasses to render
61731      * the sprites in the correct order for proper z-index stacking.
61732      * @param {Ext.draw.Sprite} sprite
61733      * @return {Number} the sprite's new index in the list
61734      */
61735     insertByZIndex: function(sprite) {
61736         var me = this,
61737             sprites = me.items.items,
61738             len = sprites.length,
61739             ceil = Math.ceil,
61740             zIndex = sprite.attr.zIndex,
61741             idx = len,
61742             high = idx - 1,
61743             low = 0,
61744             otherZIndex;
61745
61746         if (me.orderSpritesByZIndex && len && zIndex < sprites[high].attr.zIndex) {
61747             // Find the target index via a binary search for speed
61748             while (low <= high) {
61749                 idx = ceil((low + high) / 2);
61750                 otherZIndex = sprites[idx].attr.zIndex;
61751                 if (otherZIndex > zIndex) {
61752                     high = idx - 1;
61753                 }
61754                 else if (otherZIndex < zIndex) {
61755                     low = idx + 1;
61756                 }
61757                 else {
61758                     break;
61759                 }
61760             }
61761             // Step forward to the end of a sequence of the same or lower z-index
61762             while (idx < len && sprites[idx].attr.zIndex <= zIndex) {
61763                 idx++;
61764             }
61765         }
61766
61767         me.items.insert(idx, sprite);
61768         return idx;
61769     },
61770
61771     onAdd: function(sprite) {
61772         var group = sprite.group,
61773             draggable = sprite.draggable,
61774             groups, ln, i;
61775         if (group) {
61776             groups = [].concat(group);
61777             ln = groups.length;
61778             for (i = 0; i < ln; i++) {
61779                 group = groups[i];
61780                 this.getGroup(group).add(sprite);
61781             }
61782             delete sprite.group;
61783         }
61784         if (draggable) {
61785             sprite.initDraggable();
61786         }
61787     },
61788
61789     /**
61790      * Removes a given sprite from the surface, optionally destroying the sprite in the process.
61791      * You can also call the sprite own `remove` method.
61792      *
61793      * For example:
61794      *
61795      *     drawComponent.surface.remove(sprite);
61796      *     //or...
61797      *     sprite.remove();
61798      *
61799      * @param {Ext.draw.Sprite} sprite
61800      * @param {Boolean} destroySprite
61801      * @return {Number} the sprite's new index in the list
61802      */
61803     remove: function(sprite, destroySprite) {
61804         if (sprite) {
61805             this.items.remove(sprite);
61806             this.groups.each(function(item) {
61807                 item.remove(sprite);
61808             });
61809             sprite.onRemove();
61810             if (destroySprite === true) {
61811                 sprite.destroy();
61812             }
61813         }
61814     },
61815
61816     /**
61817      * Removes all sprites from the surface, optionally destroying the sprites in the process.
61818      *
61819      * For example:
61820      *
61821      *     drawComponent.surface.removeAll();
61822      *
61823      * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
61824      * @return {Number} The sprite's new index in the list.
61825      */
61826     removeAll: function(destroySprites) {
61827         var items = this.items.items,
61828             ln = items.length,
61829             i;
61830         for (i = ln - 1; i > -1; i--) {
61831             this.remove(items[i], destroySprites);
61832         }
61833     },
61834
61835     onRemove: Ext.emptyFn,
61836
61837     onDestroy: Ext.emptyFn,
61838
61839     /**
61840      * @private Using the current viewBox property and the surface's width and height, calculate the
61841      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
61842      */
61843     applyViewBox: function() {
61844         var me = this,
61845             viewBox = me.viewBox,
61846             width = me.width,
61847             height = me.height,
61848             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
61849             relativeHeight, relativeWidth, size;
61850
61851         if (viewBox && (width || height)) {
61852             viewBoxX = viewBox.x;
61853             viewBoxY = viewBox.y;
61854             viewBoxWidth = viewBox.width;
61855             viewBoxHeight = viewBox.height;
61856             relativeHeight = height / viewBoxHeight;
61857             relativeWidth = width / viewBoxWidth;
61858
61859             if (viewBoxWidth * relativeHeight < width) {
61860                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
61861             }
61862             if (viewBoxHeight * relativeWidth < height) {
61863                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
61864             }
61865
61866             size = 1 / Math.min(viewBoxWidth, relativeHeight);
61867
61868             me.viewBoxShift = {
61869                 dx: -viewBoxX,
61870                 dy: -viewBoxY,
61871                 scale: size
61872             };
61873         }
61874     },
61875
61876     transformToViewBox: function (x, y) {
61877         if (this.viewBoxShift) {
61878             var me = this, shift = me.viewBoxShift;
61879             return [x * shift.scale - shift.dx, y * shift.scale - shift.dy];
61880         } else {
61881             return [x, y];
61882         }
61883     },
61884
61885     // @private
61886     applyTransformations: function(sprite) {
61887             sprite.bbox.transform = 0;
61888             this.transform(sprite);
61889
61890         var me = this,
61891             dirty = false,
61892             attr = sprite.attr;
61893
61894         if (attr.translation.x != null || attr.translation.y != null) {
61895             me.translate(sprite);
61896             dirty = true;
61897         }
61898         if (attr.scaling.x != null || attr.scaling.y != null) {
61899             me.scale(sprite);
61900             dirty = true;
61901         }
61902         if (attr.rotation.degrees != null) {
61903             me.rotate(sprite);
61904             dirty = true;
61905         }
61906         if (dirty) {
61907             sprite.bbox.transform = 0;
61908             this.transform(sprite);
61909             sprite.transformations = [];
61910         }
61911     },
61912
61913     // @private
61914     rotate: function (sprite) {
61915         var bbox,
61916             deg = sprite.attr.rotation.degrees,
61917             centerX = sprite.attr.rotation.x,
61918             centerY = sprite.attr.rotation.y;
61919         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
61920             bbox = this.getBBox(sprite);
61921             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
61922             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
61923         }
61924         sprite.transformations.push({
61925             type: "rotate",
61926             degrees: deg,
61927             x: centerX,
61928             y: centerY
61929         });
61930     },
61931
61932     // @private
61933     translate: function(sprite) {
61934         var x = sprite.attr.translation.x || 0,
61935             y = sprite.attr.translation.y || 0;
61936         sprite.transformations.push({
61937             type: "translate",
61938             x: x,
61939             y: y
61940         });
61941     },
61942
61943     // @private
61944     scale: function(sprite) {
61945         var bbox,
61946             x = sprite.attr.scaling.x || 1,
61947             y = sprite.attr.scaling.y || 1,
61948             centerX = sprite.attr.scaling.centerX,
61949             centerY = sprite.attr.scaling.centerY;
61950
61951         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
61952             bbox = this.getBBox(sprite);
61953             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
61954             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
61955         }
61956         sprite.transformations.push({
61957             type: "scale",
61958             x: x,
61959             y: y,
61960             centerX: centerX,
61961             centerY: centerY
61962         });
61963     },
61964
61965     // @private
61966     rectPath: function (x, y, w, h, r) {
61967         if (r) {
61968             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"]];
61969         }
61970         return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
61971     },
61972
61973     // @private
61974     ellipsePath: function (x, y, rx, ry) {
61975         if (ry == null) {
61976             ry = rx;
61977         }
61978         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"]];
61979     },
61980
61981     // @private
61982     getPathpath: function (el) {
61983         return el.attr.path;
61984     },
61985
61986     // @private
61987     getPathcircle: function (el) {
61988         var a = el.attr;
61989         return this.ellipsePath(a.x, a.y, a.radius, a.radius);
61990     },
61991
61992     // @private
61993     getPathellipse: function (el) {
61994         var a = el.attr;
61995         return this.ellipsePath(a.x, a.y,
61996                                 a.radiusX || (a.width / 2) || 0,
61997                                 a.radiusY || (a.height / 2) || 0);
61998     },
61999
62000     // @private
62001     getPathrect: function (el) {
62002         var a = el.attr;
62003         return this.rectPath(a.x, a.y, a.width, a.height, a.r);
62004     },
62005
62006     // @private
62007     getPathimage: function (el) {
62008         var a = el.attr;
62009         return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
62010     },
62011
62012     // @private
62013     getPathtext: function (el) {
62014         var bbox = this.getBBoxText(el);
62015         return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
62016     },
62017
62018     createGroup: function(id) {
62019         var group = this.groups.get(id);
62020         if (!group) {
62021             group = Ext.create('Ext.draw.CompositeSprite', {
62022                 surface: this
62023             });
62024             group.id = id || Ext.id(null, 'ext-surface-group-');
62025             this.groups.add(group);
62026         }
62027         return group;
62028     },
62029
62030     /**
62031      * Returns a new group or an existent group associated with the current surface.
62032      * The group returned is a {@link Ext.draw.CompositeSprite} group.
62033      *
62034      * For example:
62035      *
62036      *     var spriteGroup = drawComponent.surface.getGroup('someGroupId');
62037      *
62038      * @param {String} id The unique identifier of the group.
62039      * @return {Object} The {@link Ext.draw.CompositeSprite}.
62040      */
62041     getGroup: function(id) {
62042         if (typeof id == "string") {
62043             var group = this.groups.get(id);
62044             if (!group) {
62045                 group = this.createGroup(id);
62046             }
62047         } else {
62048             group = id;
62049         }
62050         return group;
62051     },
62052
62053     // @private
62054     prepareItems: function(items, applyDefaults) {
62055         items = [].concat(items);
62056         // Make sure defaults are applied and item is initialized
62057         var item, i, ln;
62058         for (i = 0, ln = items.length; i < ln; i++) {
62059             item = items[i];
62060             if (!(item instanceof Ext.draw.Sprite)) {
62061                 // Temporary, just take in configs...
62062                 item.surface = this;
62063                 items[i] = this.createItem(item);
62064             } else {
62065                 item.surface = this;
62066             }
62067         }
62068         return items;
62069     },
62070
62071     /**
62072      * Changes the text in the sprite element. The sprite must be a `text` sprite.
62073      * This method can also be called from {@link Ext.draw.Sprite}.
62074      *
62075      * For example:
62076      *
62077      *     var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
62078      *
62079      * @param {Object} sprite The Sprite to change the text.
62080      * @param {String} text The new text to be set.
62081      * @method
62082      */
62083     setText: Ext.emptyFn,
62084
62085     //@private Creates an item and appends it to the surface. Called
62086     //as an internal method when calling `add`.
62087     createItem: Ext.emptyFn,
62088
62089     /**
62090      * Retrieves the id of this component.
62091      * Will autogenerate an id if one has not already been set.
62092      */
62093     getId: function() {
62094         return this.id || (this.id = Ext.id(null, 'ext-surface-'));
62095     },
62096
62097     /**
62098      * Destroys the surface. This is done by removing all components from it and
62099      * also removing its reference to a DOM element.
62100      *
62101      * For example:
62102      *
62103      *      drawComponent.surface.destroy();
62104      */
62105     destroy: function() {
62106         delete this.domRef;
62107         this.removeAll();
62108     }
62109 });
62110 /**
62111  * @class Ext.layout.component.Draw
62112  * @extends Ext.layout.component.Component
62113  * @private
62114  *
62115  */
62116
62117 Ext.define('Ext.layout.component.Draw', {
62118
62119     /* Begin Definitions */
62120
62121     alias: 'layout.draw',
62122
62123     extend: 'Ext.layout.component.Auto',
62124
62125     /* End Definitions */
62126
62127     type: 'draw',
62128
62129     onLayout : function(width, height) {
62130         this.owner.surface.setSize(width, height);
62131         this.callParent(arguments);
62132     }
62133 });
62134 /**
62135  * @class Ext.draw.Component
62136  * @extends Ext.Component
62137  *
62138  * The Draw Component is a surface in which sprites can be rendered. The Draw Component
62139  * manages and holds a `Surface` instance: an interface that has
62140  * an SVG or VML implementation depending on the browser capabilities and where
62141  * Sprites can be appended.
62142  *
62143  * One way to create a draw component is:
62144  *
62145  *     @example
62146  *     var drawComponent = Ext.create('Ext.draw.Component', {
62147  *         viewBox: false,
62148  *         items: [{
62149  *             type: 'circle',
62150  *             fill: '#79BB3F',
62151  *             radius: 100,
62152  *             x: 100,
62153  *             y: 100
62154  *         }]
62155  *     });
62156  *
62157  *     Ext.create('Ext.Window', {
62158  *         width: 215,
62159  *         height: 235,
62160  *         layout: 'fit',
62161  *         items: [drawComponent]
62162  *     }).show();
62163  *
62164  * In this case we created a draw component and added a sprite to it.
62165  * The *type* of the sprite is *circle* so if you run this code you'll see a yellow-ish
62166  * circle in a Window. When setting `viewBox` to `false` we are responsible for setting the object's position and
62167  * dimensions accordingly.
62168  *
62169  * You can also add sprites by using the surface's add method:
62170  *
62171  *     drawComponent.surface.add({
62172  *         type: 'circle',
62173  *         fill: '#79BB3F',
62174  *         radius: 100,
62175  *         x: 100,
62176  *         y: 100
62177  *     });
62178  *
62179  * For more information on Sprites, the core elements added to a draw component's surface,
62180  * refer to the Ext.draw.Sprite documentation.
62181  */
62182 Ext.define('Ext.draw.Component', {
62183
62184     /* Begin Definitions */
62185
62186     alias: 'widget.draw',
62187
62188     extend: 'Ext.Component',
62189
62190     requires: [
62191         'Ext.draw.Surface',
62192         'Ext.layout.component.Draw'
62193     ],
62194
62195     /* End Definitions */
62196
62197     /**
62198      * @cfg {String[]} enginePriority
62199      * Defines the priority order for which Surface implementation to use. The first
62200      * one supported by the current environment will be used.
62201      */
62202     enginePriority: ['Svg', 'Vml'],
62203
62204     baseCls: Ext.baseCSSPrefix + 'surface',
62205
62206     componentLayout: 'draw',
62207
62208     /**
62209      * @cfg {Boolean} viewBox
62210      * Turn on view box support which will scale and position items in the draw component to fit to the component while
62211      * maintaining aspect ratio. Note that this scaling can override other sizing settings on yor items. Defaults to true.
62212      */
62213     viewBox: true,
62214
62215     /**
62216      * @cfg {Boolean} autoSize
62217      * Turn on autoSize support which will set the bounding div's size to the natural size of the contents. Defaults to false.
62218      */
62219     autoSize: false,
62220
62221     /**
62222      * @cfg {Object[]} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
62223      * The gradients array is an array of objects with the following properties:
62224      *
62225      *  - `id` - string - The unique name of the gradient.
62226      *  - `angle` - number, optional - The angle of the gradient in degrees.
62227      *  - `stops` - object - An object with numbers as keys (from 0 to 100) and style objects as values
62228      *
62229      * ## Example
62230      *
62231      *     gradients: [{
62232      *         id: 'gradientId',
62233      *         angle: 45,
62234      *         stops: {
62235      *             0: {
62236      *                 color: '#555'
62237      *             },
62238      *             100: {
62239      *                 color: '#ddd'
62240      *             }
62241      *         }
62242      *     }, {
62243      *         id: 'gradientId2',
62244      *         angle: 0,
62245      *         stops: {
62246      *             0: {
62247      *                 color: '#590'
62248      *             },
62249      *             20: {
62250      *                 color: '#599'
62251      *             },
62252      *             100: {
62253      *                 color: '#ddd'
62254      *             }
62255      *         }
62256      *     }]
62257      *
62258      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
62259      *
62260      *     sprite.setAttributes({
62261      *         fill: 'url(#gradientId)'
62262      *     }, true);
62263      */
62264     initComponent: function() {
62265         this.callParent(arguments);
62266
62267         this.addEvents(
62268             'mousedown',
62269             'mouseup',
62270             'mousemove',
62271             'mouseenter',
62272             'mouseleave',
62273             'click'
62274         );
62275     },
62276
62277     /**
62278      * @private
62279      *
62280      * Create the Surface on initial render
62281      */
62282     onRender: function() {
62283         var me = this,
62284             viewBox = me.viewBox,
62285             autoSize = me.autoSize,
62286             bbox, items, width, height, x, y;
62287         me.callParent(arguments);
62288
62289         if (me.createSurface() !== false) {
62290             items = me.surface.items;
62291
62292             if (viewBox || autoSize) {
62293                 bbox = items.getBBox();
62294                 width = bbox.width;
62295                 height = bbox.height;
62296                 x = bbox.x;
62297                 y = bbox.y;
62298                 if (me.viewBox) {
62299                     me.surface.setViewBox(x, y, width, height);
62300                 }
62301                 else {
62302                     // AutoSized
62303                     me.autoSizeSurface();
62304                 }
62305             }
62306         }
62307     },
62308
62309     //@private
62310     autoSizeSurface: function() {
62311         var me = this,
62312             items = me.surface.items,
62313             bbox = items.getBBox(),
62314             width = bbox.width,
62315             height = bbox.height;
62316         items.setAttributes({
62317             translate: {
62318                 x: -bbox.x,
62319                 //Opera has a slight offset in the y axis.
62320                 y: -bbox.y + (+Ext.isOpera)
62321             }
62322         }, true);
62323         if (me.rendered) {
62324             me.setSize(width, height);
62325             me.surface.setSize(width, height);
62326         }
62327         else {
62328             me.surface.setSize(width, height);
62329         }
62330         me.el.setSize(width, height);
62331     },
62332
62333     /**
62334      * Create the Surface instance. Resolves the correct Surface implementation to
62335      * instantiate based on the 'enginePriority' config. Once the Surface instance is
62336      * created you can use the handle to that instance to add sprites. For example:
62337      *
62338      *     drawComponent.surface.add(sprite);
62339      */
62340     createSurface: function() {
62341         var surface = Ext.draw.Surface.create(Ext.apply({}, {
62342                 width: this.width,
62343                 height: this.height,
62344                 renderTo: this.el
62345             }, this.initialConfig));
62346         if (!surface) {
62347             // In case we cannot create a surface, return false so we can stop
62348             return false;
62349         }
62350         this.surface = surface;
62351
62352
62353         function refire(eventName) {
62354             return function(e) {
62355                 this.fireEvent(eventName, e);
62356             };
62357         }
62358
62359         surface.on({
62360             scope: this,
62361             mouseup: refire('mouseup'),
62362             mousedown: refire('mousedown'),
62363             mousemove: refire('mousemove'),
62364             mouseenter: refire('mouseenter'),
62365             mouseleave: refire('mouseleave'),
62366             click: refire('click')
62367         });
62368     },
62369
62370
62371     /**
62372      * @private
62373      *
62374      * Clean up the Surface instance on component destruction
62375      */
62376     onDestroy: function() {
62377         var surface = this.surface;
62378         if (surface) {
62379             surface.destroy();
62380         }
62381         this.callParent(arguments);
62382     }
62383
62384 });
62385
62386 /**
62387  * @class Ext.chart.LegendItem
62388  * @extends Ext.draw.CompositeSprite
62389  * A single item of a legend (marker plus label)
62390  */
62391 Ext.define('Ext.chart.LegendItem', {
62392
62393     /* Begin Definitions */
62394
62395     extend: 'Ext.draw.CompositeSprite',
62396
62397     requires: ['Ext.chart.Shape'],
62398
62399     /* End Definitions */
62400
62401     // Position of the item, relative to the upper-left corner of the legend box
62402     x: 0,
62403     y: 0,
62404     zIndex: 500,
62405
62406     constructor: function(config) {
62407         this.callParent(arguments);
62408         this.createLegend(config);
62409     },
62410
62411     /**
62412      * Creates all the individual sprites for this legend item
62413      */
62414     createLegend: function(config) {
62415         var me = this,
62416             index = config.yFieldIndex,
62417             series = me.series,
62418             seriesType = series.type,
62419             idx = me.yFieldIndex,
62420             legend = me.legend,
62421             surface = me.surface,
62422             refX = legend.x + me.x,
62423             refY = legend.y + me.y,
62424             bbox, z = me.zIndex,
62425             markerConfig, label, mask,
62426             radius, toggle = false,
62427             seriesStyle = Ext.apply(series.seriesStyle, series.style);
62428
62429         function getSeriesProp(name) {
62430             var val = series[name];
62431             return (Ext.isArray(val) ? val[idx] : val);
62432         }
62433         
62434         label = me.add('label', surface.add({
62435             type: 'text',
62436             x: 20,
62437             y: 0,
62438             zIndex: z || 0,
62439             font: legend.labelFont,
62440             text: getSeriesProp('title') || getSeriesProp('yField')
62441         }));
62442
62443         // Line series - display as short line with optional marker in the middle
62444         if (seriesType === 'line' || seriesType === 'scatter') {
62445             if(seriesType === 'line') {
62446                 me.add('line', surface.add({
62447                     type: 'path',
62448                     path: 'M0.5,0.5L16.5,0.5',
62449                     zIndex: z,
62450                     "stroke-width": series.lineWidth,
62451                     "stroke-linejoin": "round",
62452                     "stroke-dasharray": series.dash,
62453                     stroke: seriesStyle.stroke || '#000',
62454                     style: {
62455                         cursor: 'pointer'
62456                     }
62457                 }));
62458             }
62459             if (series.showMarkers || seriesType === 'scatter') {
62460                 markerConfig = Ext.apply(series.markerStyle, series.markerConfig || {});
62461                 me.add('marker', Ext.chart.Shape[markerConfig.type](surface, {
62462                     fill: markerConfig.fill,
62463                     x: 8.5,
62464                     y: 0.5,
62465                     zIndex: z,
62466                     radius: markerConfig.radius || markerConfig.size,
62467                     style: {
62468                         cursor: 'pointer'
62469                     }
62470                 }));
62471             }
62472         }
62473         // All other series types - display as filled box
62474         else {
62475             me.add('box', surface.add({
62476                 type: 'rect',
62477                 zIndex: z,
62478                 x: 0,
62479                 y: 0,
62480                 width: 12,
62481                 height: 12,
62482                 fill: series.getLegendColor(index),
62483                 style: {
62484                     cursor: 'pointer'
62485                 }
62486             }));
62487         }
62488         
62489         me.setAttributes({
62490             hidden: false
62491         }, true);
62492         
62493         bbox = me.getBBox();
62494         
62495         mask = me.add('mask', surface.add({
62496             type: 'rect',
62497             x: bbox.x,
62498             y: bbox.y,
62499             width: bbox.width || 20,
62500             height: bbox.height || 20,
62501             zIndex: (z || 0) + 1000,
62502             fill: '#f00',
62503             opacity: 0,
62504             style: {
62505                 'cursor': 'pointer'
62506             }
62507         }));
62508
62509         //add toggle listener
62510         me.on('mouseover', function() {
62511             label.setStyle({
62512                 'font-weight': 'bold'
62513             });
62514             mask.setStyle({
62515                 'cursor': 'pointer'
62516             });
62517             series._index = index;
62518             series.highlightItem();
62519         }, me);
62520
62521         me.on('mouseout', function() {
62522             label.setStyle({
62523                 'font-weight': 'normal'
62524             });
62525             series._index = index;
62526             series.unHighlightItem();
62527         }, me);
62528         
62529         if (!series.visibleInLegend(index)) {
62530             toggle = true;
62531             label.setAttributes({
62532                opacity: 0.5
62533             }, true);
62534         }
62535
62536         me.on('mousedown', function() {
62537             if (!toggle) {
62538                 series.hideAll();
62539                 label.setAttributes({
62540                     opacity: 0.5
62541                 }, true);
62542             } else {
62543                 series.showAll();
62544                 label.setAttributes({
62545                     opacity: 1
62546                 }, true);
62547             }
62548             toggle = !toggle;
62549         }, me);
62550         me.updatePosition({x:0, y:0}); //Relative to 0,0 at first so that the bbox is calculated correctly
62551     },
62552
62553     /**
62554      * Update the positions of all this item's sprites to match the root position
62555      * of the legend box.
62556      * @param {Object} relativeTo (optional) If specified, this object's 'x' and 'y' values will be used
62557      *                 as the reference point for the relative positioning. Defaults to the Legend.
62558      */
62559     updatePosition: function(relativeTo) {
62560         var me = this,
62561             items = me.items,
62562             ln = items.length,
62563             i = 0,
62564             item;
62565         if (!relativeTo) {
62566             relativeTo = me.legend;
62567         }
62568         for (; i < ln; i++) {
62569             item = items[i];
62570             switch (item.type) {
62571                 case 'text':
62572                     item.setAttributes({
62573                         x: 20 + relativeTo.x + me.x,
62574                         y: relativeTo.y + me.y
62575                     }, true);
62576                     break;
62577                 case 'rect':
62578                     item.setAttributes({
62579                         translate: {
62580                             x: relativeTo.x + me.x,
62581                             y: relativeTo.y + me.y - 6
62582                         }
62583                     }, true);
62584                     break;
62585                 default:
62586                     item.setAttributes({
62587                         translate: {
62588                             x: relativeTo.x + me.x,
62589                             y: relativeTo.y + me.y
62590                         }
62591                     }, true);
62592             }
62593         }
62594     }
62595 });
62596
62597 /**
62598  * @class Ext.chart.Legend
62599  *
62600  * Defines a legend for a chart's series.
62601  * The 'chart' member must be set prior to rendering.
62602  * The legend class displays a list of legend items each of them related with a
62603  * series being rendered. In order to render the legend item of the proper series
62604  * the series configuration object must have `showInSeries` set to true.
62605  *
62606  * The legend configuration object accepts a `position` as parameter.
62607  * The `position` parameter can be `left`, `right`
62608  * `top` or `bottom`. For example:
62609  *
62610  *     legend: {
62611  *         position: 'right'
62612  *     },
62613  *
62614  * ## Example
62615  *
62616  *     @example
62617  *     var store = Ext.create('Ext.data.JsonStore', {
62618  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
62619  *         data: [
62620  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
62621  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
62622  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
62623  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
62624  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
62625  *         ]
62626  *     });
62627  *
62628  *     Ext.create('Ext.chart.Chart', {
62629  *         renderTo: Ext.getBody(),
62630  *         width: 500,
62631  *         height: 300,
62632  *         animate: true,
62633  *         store: store,
62634  *         shadow: true,
62635  *         theme: 'Category1',
62636  *         legend: {
62637  *             position: 'top'
62638  *         },
62639  *         axes: [
62640  *             {
62641  *                 type: 'Numeric',
62642  *                 grid: true,
62643  *                 position: 'left',
62644  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
62645  *                 title: 'Sample Values',
62646  *                 grid: {
62647  *                     odd: {
62648  *                         opacity: 1,
62649  *                         fill: '#ddd',
62650  *                         stroke: '#bbb',
62651  *                         'stroke-width': 1
62652  *                     }
62653  *                 },
62654  *                 minimum: 0,
62655  *                 adjustMinimumByMajorUnit: 0
62656  *             },
62657  *             {
62658  *                 type: 'Category',
62659  *                 position: 'bottom',
62660  *                 fields: ['name'],
62661  *                 title: 'Sample Metrics',
62662  *                 grid: true,
62663  *                 label: {
62664  *                     rotate: {
62665  *                         degrees: 315
62666  *                     }
62667  *                 }
62668  *             }
62669  *         ],
62670  *         series: [{
62671  *             type: 'area',
62672  *             highlight: false,
62673  *             axis: 'left',
62674  *             xField: 'name',
62675  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
62676  *             style: {
62677  *                 opacity: 0.93
62678  *             }
62679  *         }]
62680  *     });
62681  */
62682 Ext.define('Ext.chart.Legend', {
62683
62684     /* Begin Definitions */
62685
62686     requires: ['Ext.chart.LegendItem'],
62687
62688     /* End Definitions */
62689
62690     /**
62691      * @cfg {Boolean} visible
62692      * Whether or not the legend should be displayed.
62693      */
62694     visible: true,
62695
62696     /**
62697      * @cfg {String} position
62698      * The position of the legend in relation to the chart. One of: "top",
62699      * "bottom", "left", "right", or "float". If set to "float", then the legend
62700      * box will be positioned at the point denoted by the x and y parameters.
62701      */
62702     position: 'bottom',
62703
62704     /**
62705      * @cfg {Number} x
62706      * X-position of the legend box. Used directly if position is set to "float", otherwise
62707      * it will be calculated dynamically.
62708      */
62709     x: 0,
62710
62711     /**
62712      * @cfg {Number} y
62713      * Y-position of the legend box. Used directly if position is set to "float", otherwise
62714      * it will be calculated dynamically.
62715      */
62716     y: 0,
62717
62718     /**
62719      * @cfg {String} labelFont
62720      * Font to be used for the legend labels, eg '12px Helvetica'
62721      */
62722     labelFont: '12px Helvetica, sans-serif',
62723
62724     /**
62725      * @cfg {String} boxStroke
62726      * Style of the stroke for the legend box
62727      */
62728     boxStroke: '#000',
62729
62730     /**
62731      * @cfg {String} boxStrokeWidth
62732      * Width of the stroke for the legend box
62733      */
62734     boxStrokeWidth: 1,
62735
62736     /**
62737      * @cfg {String} boxFill
62738      * Fill style for the legend box
62739      */
62740     boxFill: '#FFF',
62741
62742     /**
62743      * @cfg {Number} itemSpacing
62744      * Amount of space between legend items
62745      */
62746     itemSpacing: 10,
62747
62748     /**
62749      * @cfg {Number} padding
62750      * Amount of padding between the legend box's border and its items
62751      */
62752     padding: 5,
62753
62754     // @private
62755     width: 0,
62756     // @private
62757     height: 0,
62758
62759     /**
62760      * @cfg {Number} boxZIndex
62761      * Sets the z-index for the legend. Defaults to 100.
62762      */
62763     boxZIndex: 100,
62764
62765     /**
62766      * Creates new Legend.
62767      * @param {Object} config  (optional) Config object.
62768      */
62769     constructor: function(config) {
62770         var me = this;
62771         if (config) {
62772             Ext.apply(me, config);
62773         }
62774         me.items = [];
62775         /**
62776          * Whether the legend box is oriented vertically, i.e. if it is on the left or right side or floating.
62777          * @type {Boolean}
62778          */
62779         me.isVertical = ("left|right|float".indexOf(me.position) !== -1);
62780
62781         // cache these here since they may get modified later on
62782         me.origX = me.x;
62783         me.origY = me.y;
62784     },
62785
62786     /**
62787      * @private Create all the sprites for the legend
62788      */
62789     create: function() {
62790         var me = this;
62791         me.createBox();
62792         me.createItems();
62793         if (!me.created && me.isDisplayed()) {
62794             me.created = true;
62795
62796             // Listen for changes to series titles to trigger regeneration of the legend
62797             me.chart.series.each(function(series) {
62798                 series.on('titlechange', function() {
62799                     me.create();
62800                     me.updatePosition();
62801                 });
62802             });
62803         }
62804     },
62805
62806     /**
62807      * @private Determine whether the legend should be displayed. Looks at the legend's 'visible' config,
62808      * and also the 'showInLegend' config for each of the series.
62809      */
62810     isDisplayed: function() {
62811         return this.visible && this.chart.series.findIndex('showInLegend', true) !== -1;
62812     },
62813
62814     /**
62815      * @private Create the series markers and labels
62816      */
62817     createItems: function() {
62818         var me = this,
62819             chart = me.chart,
62820             surface = chart.surface,
62821             items = me.items,
62822             padding = me.padding,
62823             itemSpacing = me.itemSpacing,
62824             spacingOffset = 2,
62825             maxWidth = 0,
62826             maxHeight = 0,
62827             totalWidth = 0,
62828             totalHeight = 0,
62829             vertical = me.isVertical,
62830             math = Math,
62831             mfloor = math.floor,
62832             mmax = math.max,
62833             index = 0,
62834             i = 0,
62835             len = items ? items.length : 0,
62836             x, y, spacing, item, bbox, height, width;
62837
62838         //remove all legend items
62839         if (len) {
62840             for (; i < len; i++) {
62841                 items[i].destroy();
62842             }
62843         }
62844         //empty array
62845         items.length = [];
62846         // Create all the item labels, collecting their dimensions and positioning each one
62847         // properly in relation to the previous item
62848         chart.series.each(function(series, i) {
62849             if (series.showInLegend) {
62850                 Ext.each([].concat(series.yField), function(field, j) {
62851                     item = Ext.create('Ext.chart.LegendItem', {
62852                         legend: this,
62853                         series: series,
62854                         surface: chart.surface,
62855                         yFieldIndex: j
62856                     });
62857                     bbox = item.getBBox();
62858
62859                     //always measure from x=0, since not all markers go all the way to the left
62860                     width = bbox.width;
62861                     height = bbox.height;
62862
62863                     if (i + j === 0) {
62864                         spacing = vertical ? padding + height / 2 : padding;
62865                     }
62866                     else {
62867                         spacing = itemSpacing / (vertical ? 2 : 1);
62868                     }
62869                     // Set the item's position relative to the legend box
62870                     item.x = mfloor(vertical ? padding : totalWidth + spacing);
62871                     item.y = mfloor(vertical ? totalHeight + spacing : padding + height / 2);
62872
62873                     // Collect cumulative dimensions
62874                     totalWidth += width + spacing;
62875                     totalHeight += height + spacing;
62876                     maxWidth = mmax(maxWidth, width);
62877                     maxHeight = mmax(maxHeight, height);
62878
62879                     items.push(item);
62880                 }, this);
62881             }
62882         }, me);
62883
62884         // Store the collected dimensions for later
62885         me.width = mfloor((vertical ? maxWidth : totalWidth) + padding * 2);
62886         if (vertical && items.length === 1) {
62887             spacingOffset = 1;
62888         }
62889         me.height = mfloor((vertical ? totalHeight - spacingOffset * spacing : maxHeight) + (padding * 2));
62890         me.itemHeight = maxHeight;
62891     },
62892
62893     /**
62894      * @private Get the bounds for the legend's outer box
62895      */
62896     getBBox: function() {
62897         var me = this;
62898         return {
62899             x: Math.round(me.x) - me.boxStrokeWidth / 2,
62900             y: Math.round(me.y) - me.boxStrokeWidth / 2,
62901             width: me.width,
62902             height: me.height
62903         };
62904     },
62905
62906     /**
62907      * @private Create the box around the legend items
62908      */
62909     createBox: function() {
62910         var me = this,
62911             box;
62912
62913         if (me.boxSprite) {
62914             me.boxSprite.destroy();
62915         }
62916         
62917         box = me.boxSprite = me.chart.surface.add(Ext.apply({
62918             type: 'rect',
62919             stroke: me.boxStroke,
62920             "stroke-width": me.boxStrokeWidth,
62921             fill: me.boxFill,
62922             zIndex: me.boxZIndex
62923         }, me.getBBox()));
62924
62925         box.redraw();
62926     },
62927
62928     /**
62929      * @private Update the position of all the legend's sprites to match its current x/y values
62930      */
62931     updatePosition: function() {
62932         var me = this,
62933             x, y,
62934             legendWidth = me.width,
62935             legendHeight = me.height,
62936             padding = me.padding,
62937             chart = me.chart,
62938             chartBBox = chart.chartBBox,
62939             insets = chart.insetPadding,
62940             chartWidth = chartBBox.width - (insets * 2),
62941             chartHeight = chartBBox.height - (insets * 2),
62942             chartX = chartBBox.x + insets,
62943             chartY = chartBBox.y + insets,
62944             surface = chart.surface,
62945             mfloor = Math.floor;
62946
62947         if (me.isDisplayed()) {
62948             // Find the position based on the dimensions
62949             switch(me.position) {
62950                 case "left":
62951                     x = insets;
62952                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
62953                     break;
62954                 case "right":
62955                     x = mfloor(surface.width - legendWidth) - insets;
62956                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
62957                     break;
62958                 case "top":
62959                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
62960                     y = insets;
62961                     break;
62962                 case "bottom":
62963                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
62964                     y = mfloor(surface.height - legendHeight) - insets;
62965                     break;
62966                 default:
62967                     x = mfloor(me.origX) + insets;
62968                     y = mfloor(me.origY) + insets;
62969             }
62970             me.x = x;
62971             me.y = y;
62972
62973             // Update the position of each item
62974             Ext.each(me.items, function(item) {
62975                 item.updatePosition();
62976             });
62977             // Update the position of the outer box
62978             me.boxSprite.setAttributes(me.getBBox(), true);
62979         }
62980     }
62981 });
62982
62983 /**
62984  * Charts provide a flexible way to achieve a wide range of data visualization capablitities.
62985  * Each Chart gets its data directly from a {@link Ext.data.Store Store}, and automatically
62986  * updates its display whenever data in the Store changes. In addition, the look and feel
62987  * of a Chart can be customized using {@link Ext.chart.theme.Theme Theme}s.
62988  * 
62989  * ## Creating a Simple Chart
62990  * 
62991  * Every Chart has three key parts - a {@link Ext.data.Store Store} that contains the data,
62992  * an array of {@link Ext.chart.axis.Axis Axes} which define the boundaries of the Chart,
62993  * and one or more {@link Ext.chart.series.Series Series} to handle the visual rendering of the data points.
62994  * 
62995  * ### 1. Creating a Store
62996  * 
62997  * The first step is to create a {@link Ext.data.Model Model} that represents the type of
62998  * data that will be displayed in the Chart. For example the data for a chart that displays
62999  * a weather forecast could be represented as a series of "WeatherPoint" data points with
63000  * two fields - "temperature", and "date":
63001  * 
63002  *     Ext.define('WeatherPoint', {
63003  *         extend: 'Ext.data.Model',
63004  *         fields: ['temperature', 'date']
63005  *     });
63006  * 
63007  * Next a {@link Ext.data.Store Store} must be created.  The store contains a collection of "WeatherPoint" Model instances.
63008  * The data could be loaded dynamically, but for sake of ease this example uses inline data:
63009  * 
63010  *     var store = Ext.create('Ext.data.Store', {
63011  *         model: 'WeatherPoint',
63012  *         data: [
63013  *             { temperature: 58, date: new Date(2011, 1, 1, 8) },
63014  *             { temperature: 63, date: new Date(2011, 1, 1, 9) },
63015  *             { temperature: 73, date: new Date(2011, 1, 1, 10) },
63016  *             { temperature: 78, date: new Date(2011, 1, 1, 11) },
63017  *             { temperature: 81, date: new Date(2011, 1, 1, 12) }
63018  *         ]
63019  *     });
63020  *    
63021  * For additional information on Models and Stores please refer to the [Data Guide](#/guide/data).
63022  * 
63023  * ### 2. Creating the Chart object
63024  * 
63025  * Now that a Store has been created it can be used in a Chart:
63026  * 
63027  *     Ext.create('Ext.chart.Chart', {
63028  *        renderTo: Ext.getBody(),
63029  *        width: 400,
63030  *        height: 300,
63031  *        store: store
63032  *     });
63033  *    
63034  * That's all it takes to create a Chart instance that is backed by a Store.
63035  * However, if the above code is run in a browser, a blank screen will be displayed.
63036  * This is because the two pieces that are responsible for the visual display,
63037  * the Chart's {@link #cfg-axes axes} and {@link #cfg-series series}, have not yet been defined.
63038  * 
63039  * ### 3. Configuring the Axes
63040  * 
63041  * {@link Ext.chart.axis.Axis Axes} are the lines that define the boundaries of the data points that a Chart can display.
63042  * This example uses one of the most common Axes configurations - a horizontal "x" axis, and a vertical "y" axis:
63043  * 
63044  *     Ext.create('Ext.chart.Chart', {
63045  *         ...
63046  *         axes: [
63047  *             {
63048  *                 title: 'Temperature',
63049  *                 type: 'Numeric',
63050  *                 position: 'left',
63051  *                 fields: ['temperature'],
63052  *                 minimum: 0,
63053  *                 maximum: 100
63054  *             },
63055  *             {
63056  *                 title: 'Time',
63057  *                 type: 'Time',
63058  *                 position: 'bottom',
63059  *                 fields: ['date'],
63060  *                 dateFormat: 'ga'
63061  *             }
63062  *         ]
63063  *     });
63064  *    
63065  * The "Temperature" axis is a vertical {@link Ext.chart.axis.Numeric Numeric Axis} and is positioned on the left edge of the Chart.
63066  * It represents the bounds of the data contained in the "WeatherPoint" Model's "temperature" field that was
63067  * defined above. The minimum value for this axis is "0", and the maximum is "100".
63068  * 
63069  * The horizontal axis is a {@link Ext.chart.axis.Time Time Axis} and is positioned on the bottom edge of the Chart.
63070  * It represents the bounds of the data contained in the "WeatherPoint" Model's "date" field.
63071  * The {@link Ext.chart.axis.Time#cfg-dateFormat dateFormat}
63072  * configuration tells the Time Axis how to format it's labels.
63073  * 
63074  * Here's what the Chart looks like now that it has its Axes configured:
63075  * 
63076  * {@img Ext.chart.Chart/Ext.chart.Chart1.png Chart Axes}
63077  * 
63078  * ### 4. Configuring the Series
63079  * 
63080  * The final step in creating a simple Chart is to configure one or more {@link Ext.chart.series.Series Series}.
63081  * Series are responsible for the visual representation of the data points contained in the Store.
63082  * This example only has one Series:
63083  * 
63084  *     Ext.create('Ext.chart.Chart', {
63085  *         ...
63086  *         axes: [
63087  *             ...
63088  *         ],
63089  *         series: [
63090  *             {
63091  *                 type: 'line',
63092  *                 xField: 'date',
63093  *                 yField: 'temperature'
63094  *             }
63095  *         ]
63096  *     });
63097  *     
63098  * This Series is a {@link Ext.chart.series.Line Line Series}, and it uses the "date" and "temperature" fields
63099  * from the "WeatherPoint" Models in the Store to plot its data points:
63100  * 
63101  * {@img Ext.chart.Chart/Ext.chart.Chart2.png Line Series}
63102  * 
63103  * See the [Simple Chart Example](doc-resources/Ext.chart.Chart/examples/simple_chart/index.html) for a live demo.
63104  * 
63105  * ## Themes
63106  * 
63107  * The color scheme for a Chart can be easily changed using the {@link #cfg-theme theme} configuration option:
63108  * 
63109  *     Ext.create('Ext.chart.Chart', {
63110  *         ...
63111  *         theme: 'Green',
63112  *         ...
63113  *     });
63114  * 
63115  * {@img Ext.chart.Chart/Ext.chart.Chart3.png Green Theme}
63116  * 
63117  * For more information on Charts please refer to the [Drawing and Charting Guide](#/guide/drawing_and_charting).
63118  * 
63119  */
63120 Ext.define('Ext.chart.Chart', {
63121
63122     /* Begin Definitions */
63123
63124     alias: 'widget.chart',
63125
63126     extend: 'Ext.draw.Component',
63127     
63128     mixins: {
63129         themeManager: 'Ext.chart.theme.Theme',
63130         mask: 'Ext.chart.Mask',
63131         navigation: 'Ext.chart.Navigation'
63132     },
63133
63134     requires: [
63135         'Ext.util.MixedCollection',
63136         'Ext.data.StoreManager',
63137         'Ext.chart.Legend',
63138         'Ext.util.DelayedTask'
63139     ],
63140
63141     /* End Definitions */
63142
63143     // @private
63144     viewBox: false,
63145
63146     /**
63147      * @cfg {String} theme
63148      * The name of the theme to be used. A theme defines the colors and other visual displays of tick marks
63149      * on axis, text, title text, line colors, marker colors and styles, etc. Possible theme values are 'Base', 'Green',
63150      * 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes 'Category1' to 'Category6'. Default value
63151      * is 'Base'.
63152      */
63153
63154     /**
63155      * @cfg {Boolean/Object} animate
63156      * True for the default animation (easing: 'ease' and duration: 500) or a standard animation config
63157      * object to be used for default chart animations. Defaults to false.
63158      */
63159     animate: false,
63160
63161     /**
63162      * @cfg {Boolean/Object} legend
63163      * True for the default legend display or a legend config object. Defaults to false.
63164      */
63165     legend: false,
63166
63167     /**
63168      * @cfg {Number} insetPadding
63169      * The amount of inset padding in pixels for the chart. Defaults to 10.
63170      */
63171     insetPadding: 10,
63172
63173     /**
63174      * @cfg {String[]} enginePriority
63175      * Defines the priority order for which Surface implementation to use. The first one supported by the current
63176      * environment will be used. Defaults to `['Svg', 'Vml']`.
63177      */
63178     enginePriority: ['Svg', 'Vml'],
63179
63180     /**
63181      * @cfg {Object/Boolean} background
63182      * The chart background. This can be a gradient object, image, or color. Defaults to false for no
63183      * background. For example, if `background` were to be a color we could set the object as
63184      *
63185      *     background: {
63186      *         //color string
63187      *         fill: '#ccc'
63188      *     }
63189      *
63190      * You can specify an image by using:
63191      *
63192      *     background: {
63193      *         image: 'http://path.to.image/'
63194      *     }
63195      *
63196      * Also you can specify a gradient by using the gradient object syntax:
63197      *
63198      *     background: {
63199      *         gradient: {
63200      *             id: 'gradientId',
63201      *             angle: 45,
63202      *             stops: {
63203      *                 0: {
63204      *                     color: '#555'
63205      *                 }
63206      *                 100: {
63207      *                     color: '#ddd'
63208      *                 }
63209      *             }
63210      *         }
63211      *     }
63212      */
63213     background: false,
63214
63215     /**
63216      * @cfg {Object[]} gradients
63217      * Define a set of gradients that can be used as `fill` property in sprites. The gradients array is an
63218      * array of objects with the following properties:
63219      *
63220      * - **id** - string - The unique name of the gradient.
63221      * - **angle** - number, optional - The angle of the gradient in degrees.
63222      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values
63223      *
63224      * For example:
63225      *
63226      *     gradients: [{
63227      *         id: 'gradientId',
63228      *         angle: 45,
63229      *         stops: {
63230      *             0: {
63231      *                 color: '#555'
63232      *             },
63233      *             100: {
63234      *                 color: '#ddd'
63235      *             }
63236      *         }
63237      *     }, {
63238      *         id: 'gradientId2',
63239      *         angle: 0,
63240      *         stops: {
63241      *             0: {
63242      *                 color: '#590'
63243      *             },
63244      *             20: {
63245      *                 color: '#599'
63246      *             },
63247      *             100: {
63248      *                 color: '#ddd'
63249      *             }
63250      *         }
63251      *     }]
63252      *
63253      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
63254      *
63255      *     sprite.setAttributes({
63256      *         fill: 'url(#gradientId)'
63257      *     }, true);
63258      */
63259
63260     /**
63261      * @cfg {Ext.data.Store} store
63262      * The store that supplies data to this chart.
63263      */
63264
63265     /**
63266      * @cfg {Ext.chart.series.Series[]} series
63267      * Array of {@link Ext.chart.series.Series Series} instances or config objects.  For example:
63268      * 
63269      *     series: [{
63270      *         type: 'column',
63271      *         axis: 'left',
63272      *         listeners: {
63273      *             'afterrender': function() {
63274      *                 console('afterrender');
63275      *             }
63276      *         },
63277      *         xField: 'category',
63278      *         yField: 'data1'
63279      *     }]
63280      */
63281
63282     /**
63283      * @cfg {Ext.chart.axis.Axis[]} axes
63284      * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects.  For example:
63285      * 
63286      *     axes: [{
63287      *         type: 'Numeric',
63288      *         position: 'left',
63289      *         fields: ['data1'],
63290      *         title: 'Number of Hits',
63291      *         minimum: 0,
63292      *         //one minor tick between two major ticks
63293      *         minorTickSteps: 1
63294      *     }, {
63295      *         type: 'Category',
63296      *         position: 'bottom',
63297      *         fields: ['name'],
63298      *         title: 'Month of the Year'
63299      *     }]
63300      */
63301
63302     constructor: function(config) {
63303         var me = this,
63304             defaultAnim;
63305             
63306         config = Ext.apply({}, config);
63307         me.initTheme(config.theme || me.theme);
63308         if (me.gradients) {
63309             Ext.apply(config, { gradients: me.gradients });
63310         }
63311         if (me.background) {
63312             Ext.apply(config, { background: me.background });
63313         }
63314         if (config.animate) {
63315             defaultAnim = {
63316                 easing: 'ease',
63317                 duration: 500
63318             };
63319             if (Ext.isObject(config.animate)) {
63320                 config.animate = Ext.applyIf(config.animate, defaultAnim);
63321             }
63322             else {
63323                 config.animate = defaultAnim;
63324             }
63325         }
63326         me.mixins.mask.constructor.call(me, config);
63327         me.mixins.navigation.constructor.call(me, config);
63328         me.callParent([config]);
63329     },
63330     
63331     getChartStore: function(){
63332         return this.substore || this.store;    
63333     },
63334
63335     initComponent: function() {
63336         var me = this,
63337             axes,
63338             series;
63339         me.callParent();
63340         me.addEvents(
63341             'itemmousedown',
63342             'itemmouseup',
63343             'itemmouseover',
63344             'itemmouseout',
63345             'itemclick',
63346             'itemdoubleclick',
63347             'itemdragstart',
63348             'itemdrag',
63349             'itemdragend',
63350             /**
63351              * @event beforerefresh
63352              * Fires before a refresh to the chart data is called. If the beforerefresh handler returns false the
63353              * {@link #refresh} action will be cancelled.
63354              * @param {Ext.chart.Chart} this
63355              */
63356             'beforerefresh',
63357             /**
63358              * @event refresh
63359              * Fires after the chart data has been refreshed.
63360              * @param {Ext.chart.Chart} this
63361              */
63362             'refresh'
63363         );
63364         Ext.applyIf(me, {
63365             zoom: {
63366                 width: 1,
63367                 height: 1,
63368                 x: 0,
63369                 y: 0
63370             }
63371         });
63372         me.maxGutter = [0, 0];
63373         me.store = Ext.data.StoreManager.lookup(me.store);
63374         axes = me.axes;
63375         me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
63376         if (axes) {
63377             me.axes.addAll(axes);
63378         }
63379         series = me.series;
63380         me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
63381         if (series) {
63382             me.series.addAll(series);
63383         }
63384         if (me.legend !== false) {
63385             me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
63386         }
63387
63388         me.on({
63389             mousemove: me.onMouseMove,
63390             mouseleave: me.onMouseLeave,
63391             mousedown: me.onMouseDown,
63392             mouseup: me.onMouseUp,
63393             scope: me
63394         });
63395     },
63396
63397     // @private overrides the component method to set the correct dimensions to the chart.
63398     afterComponentLayout: function(width, height) {
63399         var me = this;
63400         if (Ext.isNumber(width) && Ext.isNumber(height)) {
63401             me.curWidth = width;
63402             me.curHeight = height;
63403             me.redraw(true);
63404         }
63405         this.callParent(arguments);
63406     },
63407
63408     /**
63409      * Redraws the chart. If animations are set this will animate the chart too. 
63410      * @param {Boolean} resize (optional) flag which changes the default origin points of the chart for animations.
63411      */
63412     redraw: function(resize) {
63413         var me = this,
63414             chartBBox = me.chartBBox = {
63415                 x: 0,
63416                 y: 0,
63417                 height: me.curHeight,
63418                 width: me.curWidth
63419             },
63420             legend = me.legend;
63421         me.surface.setSize(chartBBox.width, chartBBox.height);
63422         // Instantiate Series and Axes
63423         me.series.each(me.initializeSeries, me);
63424         me.axes.each(me.initializeAxis, me);
63425         //process all views (aggregated data etc) on stores
63426         //before rendering.
63427         me.axes.each(function(axis) {
63428             axis.processView();
63429         });
63430         me.axes.each(function(axis) {
63431             axis.drawAxis(true);
63432         });
63433
63434         // Create legend if not already created
63435         if (legend !== false) {
63436             legend.create();
63437         }
63438
63439         // Place axes properly, including influence from each other
63440         me.alignAxes();
63441
63442         // Reposition legend based on new axis alignment
63443         if (me.legend !== false) {
63444             legend.updatePosition();
63445         }
63446
63447         // Find the max gutter
63448         me.getMaxGutter();
63449
63450         // Draw axes and series
63451         me.resizing = !!resize;
63452
63453         me.axes.each(me.drawAxis, me);
63454         me.series.each(me.drawCharts, me);
63455         me.resizing = false;
63456     },
63457
63458     // @private set the store after rendering the chart.
63459     afterRender: function() {
63460         var ref,
63461             me = this;
63462         this.callParent();
63463
63464         if (me.categoryNames) {
63465             me.setCategoryNames(me.categoryNames);
63466         }
63467
63468         if (me.tipRenderer) {
63469             ref = me.getFunctionRef(me.tipRenderer);
63470             me.setTipRenderer(ref.fn, ref.scope);
63471         }
63472         me.bindStore(me.store, true);
63473         me.refresh();
63474     },
63475
63476     // @private get x and y position of the mouse cursor.
63477     getEventXY: function(e) {
63478         var me = this,
63479             box = this.surface.getRegion(),
63480             pageXY = e.getXY(),
63481             x = pageXY[0] - box.left,
63482             y = pageXY[1] - box.top;
63483         return [x, y];
63484     },
63485
63486     // @private wrap the mouse down position to delegate the event to the series.
63487     onClick: function(e) {
63488         var me = this,
63489             position = me.getEventXY(e),
63490             item;
63491
63492         // Ask each series if it has an item corresponding to (not necessarily exactly
63493         // on top of) the current mouse coords. Fire itemclick event.
63494         me.series.each(function(series) {
63495             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
63496                 if (series.getItemForPoint) {
63497                     item = series.getItemForPoint(position[0], position[1]);
63498                     if (item) {
63499                         series.fireEvent('itemclick', item);
63500                     }
63501                 }
63502             }
63503         }, me);
63504     },
63505
63506     // @private wrap the mouse down position to delegate the event to the series.
63507     onMouseDown: function(e) {
63508         var me = this,
63509             position = me.getEventXY(e),
63510             item;
63511
63512         if (me.mask) {
63513             me.mixins.mask.onMouseDown.call(me, e);
63514         }
63515         // Ask each series if it has an item corresponding to (not necessarily exactly
63516         // on top of) the current mouse coords. Fire mousedown event.
63517         me.series.each(function(series) {
63518             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
63519                 if (series.getItemForPoint) {
63520                     item = series.getItemForPoint(position[0], position[1]);
63521                     if (item) {
63522                         series.fireEvent('itemmousedown', item);
63523                     }
63524                 }
63525             }
63526         }, me);
63527     },
63528
63529     // @private wrap the mouse up event to delegate it to the series.
63530     onMouseUp: function(e) {
63531         var me = this,
63532             position = me.getEventXY(e),
63533             item;
63534
63535         if (me.mask) {
63536             me.mixins.mask.onMouseUp.call(me, e);
63537         }
63538         // Ask each series if it has an item corresponding to (not necessarily exactly
63539         // on top of) the current mouse coords. Fire mousedown event.
63540         me.series.each(function(series) {
63541             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
63542                 if (series.getItemForPoint) {
63543                     item = series.getItemForPoint(position[0], position[1]);
63544                     if (item) {
63545                         series.fireEvent('itemmouseup', item);
63546                     }
63547                 }
63548             }
63549         }, me);
63550     },
63551
63552     // @private wrap the mouse move event so it can be delegated to the series.
63553     onMouseMove: function(e) {
63554         var me = this,
63555             position = me.getEventXY(e),
63556             item, last, storeItem, storeField;
63557
63558         if (me.mask) {
63559             me.mixins.mask.onMouseMove.call(me, e);
63560         }
63561         // Ask each series if it has an item corresponding to (not necessarily exactly
63562         // on top of) the current mouse coords. Fire itemmouseover/out events.
63563         me.series.each(function(series) {
63564             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
63565                 if (series.getItemForPoint) {
63566                     item = series.getItemForPoint(position[0], position[1]);
63567                     last = series._lastItemForPoint;
63568                     storeItem = series._lastStoreItem;
63569                     storeField = series._lastStoreField;
63570
63571
63572                     if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
63573                         if (last) {
63574                             series.fireEvent('itemmouseout', last);
63575                             delete series._lastItemForPoint;
63576                             delete series._lastStoreField;
63577                             delete series._lastStoreItem;
63578                         }
63579                         if (item) {
63580                             series.fireEvent('itemmouseover', item);
63581                             series._lastItemForPoint = item;
63582                             series._lastStoreItem = item.storeItem;
63583                             series._lastStoreField = item.storeField;
63584                         }
63585                     }
63586                 }
63587             } else {
63588                 last = series._lastItemForPoint;
63589                 if (last) {
63590                     series.fireEvent('itemmouseout', last);
63591                     delete series._lastItemForPoint;
63592                     delete series._lastStoreField;
63593                     delete series._lastStoreItem;
63594                 }
63595             }
63596         }, me);
63597     },
63598
63599     // @private handle mouse leave event.
63600     onMouseLeave: function(e) {
63601         var me = this;
63602         if (me.mask) {
63603             me.mixins.mask.onMouseLeave.call(me, e);
63604         }
63605         me.series.each(function(series) {
63606             delete series._lastItemForPoint;
63607         });
63608     },
63609
63610     // @private buffered refresh for when we update the store
63611     delayRefresh: function() {
63612         var me = this;
63613         if (!me.refreshTask) {
63614             me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
63615         }
63616         me.refreshTask.delay(me.refreshBuffer);
63617     },
63618
63619     // @private
63620     refresh: function() {
63621         var me = this;
63622         if (me.rendered && me.curWidth !== undefined && me.curHeight !== undefined) {
63623             if (me.fireEvent('beforerefresh', me) !== false) {
63624                 me.redraw();
63625                 me.fireEvent('refresh', me);
63626             }
63627         }
63628     },
63629
63630     /**
63631      * Changes the data store bound to this chart and refreshes it.
63632      * @param {Ext.data.Store} store The store to bind to this chart
63633      */
63634     bindStore: function(store, initial) {
63635         var me = this;
63636         if (!initial && me.store) {
63637             if (store !== me.store && me.store.autoDestroy) {
63638                 me.store.destroyStore();
63639             }
63640             else {
63641                 me.store.un('datachanged', me.refresh, me);
63642                 me.store.un('add', me.delayRefresh, me);
63643                 me.store.un('remove', me.delayRefresh, me);
63644                 me.store.un('update', me.delayRefresh, me);
63645                 me.store.un('clear', me.refresh, me);
63646             }
63647         }
63648         if (store) {
63649             store = Ext.data.StoreManager.lookup(store);
63650             store.on({
63651                 scope: me,
63652                 datachanged: me.refresh,
63653                 add: me.delayRefresh,
63654                 remove: me.delayRefresh,
63655                 update: me.delayRefresh,
63656                 clear: me.refresh
63657             });
63658         }
63659         me.store = store;
63660         if (store && !initial) {
63661             me.refresh();
63662         }
63663     },
63664
63665     // @private Create Axis
63666     initializeAxis: function(axis) {
63667         var me = this,
63668             chartBBox = me.chartBBox,
63669             w = chartBBox.width,
63670             h = chartBBox.height,
63671             x = chartBBox.x,
63672             y = chartBBox.y,
63673             themeAttrs = me.themeAttrs,
63674             config = {
63675                 chart: me
63676             };
63677         if (themeAttrs) {
63678             config.axisStyle = Ext.apply({}, themeAttrs.axis);
63679             config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
63680             config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
63681             config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
63682             config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
63683             config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
63684             config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
63685             config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
63686             config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
63687         }
63688         switch (axis.position) {
63689             case 'top':
63690                 Ext.apply(config, {
63691                     length: w,
63692                     width: h,
63693                     x: x,
63694                     y: y
63695                 });
63696             break;
63697             case 'bottom':
63698                 Ext.apply(config, {
63699                     length: w,
63700                     width: h,
63701                     x: x,
63702                     y: h
63703                 });
63704             break;
63705             case 'left':
63706                 Ext.apply(config, {
63707                     length: h,
63708                     width: w,
63709                     x: x,
63710                     y: h
63711                 });
63712             break;
63713             case 'right':
63714                 Ext.apply(config, {
63715                     length: h,
63716                     width: w,
63717                     x: w,
63718                     y: h
63719                 });
63720             break;
63721         }
63722         if (!axis.chart) {
63723             Ext.apply(config, axis);
63724             axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
63725         }
63726         else {
63727             Ext.apply(axis, config);
63728         }
63729     },
63730
63731
63732     /**
63733      * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
63734      * for the space taken up on each side by the axes and legend.
63735      */
63736     alignAxes: function() {
63737         var me = this,
63738             axes = me.axes,
63739             legend = me.legend,
63740             edges = ['top', 'right', 'bottom', 'left'],
63741             chartBBox,
63742             insetPadding = me.insetPadding,
63743             insets = {
63744                 top: insetPadding,
63745                 right: insetPadding,
63746                 bottom: insetPadding,
63747                 left: insetPadding
63748             };
63749
63750         function getAxis(edge) {
63751             var i = axes.findIndex('position', edge);
63752             return (i < 0) ? null : axes.getAt(i);
63753         }
63754
63755         // Find the space needed by axes and legend as a positive inset from each edge
63756         Ext.each(edges, function(edge) {
63757             var isVertical = (edge === 'left' || edge === 'right'),
63758                 axis = getAxis(edge),
63759                 bbox;
63760
63761             // Add legend size if it's on this edge
63762             if (legend !== false) {
63763                 if (legend.position === edge) {
63764                     bbox = legend.getBBox();
63765                     insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
63766                 }
63767             }
63768
63769             // Add axis size if there's one on this edge only if it has been
63770             //drawn before.
63771             if (axis && axis.bbox) {
63772                 bbox = axis.bbox;
63773                 insets[edge] += (isVertical ? bbox.width : bbox.height);
63774             }
63775         });
63776         // Build the chart bbox based on the collected inset values
63777         chartBBox = {
63778             x: insets.left,
63779             y: insets.top,
63780             width: me.curWidth - insets.left - insets.right,
63781             height: me.curHeight - insets.top - insets.bottom
63782         };
63783         me.chartBBox = chartBBox;
63784
63785         // Go back through each axis and set its length and position based on the
63786         // corresponding edge of the chartBBox
63787         axes.each(function(axis) {
63788             var pos = axis.position,
63789                 isVertical = (pos === 'left' || pos === 'right');
63790
63791             axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
63792             axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
63793             axis.width = (isVertical ? chartBBox.width : chartBBox.height);
63794             axis.length = (isVertical ? chartBBox.height : chartBBox.width);
63795         });
63796     },
63797
63798     // @private initialize the series.
63799     initializeSeries: function(series, idx) {
63800         var me = this,
63801             themeAttrs = me.themeAttrs,
63802             seriesObj, markerObj, seriesThemes, st,
63803             markerThemes, colorArrayStyle = [],
63804             i = 0, l,
63805             config = {
63806                 chart: me,
63807                 seriesId: series.seriesId
63808             };
63809         if (themeAttrs) {
63810             seriesThemes = themeAttrs.seriesThemes;
63811             markerThemes = themeAttrs.markerThemes;
63812             seriesObj = Ext.apply({}, themeAttrs.series);
63813             markerObj = Ext.apply({}, themeAttrs.marker);
63814             config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
63815             config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
63816             config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
63817             if (themeAttrs.colors) {
63818                 config.colorArrayStyle = themeAttrs.colors;
63819             } else {
63820                 colorArrayStyle = [];
63821                 for (l = seriesThemes.length; i < l; i++) {
63822                     st = seriesThemes[i];
63823                     if (st.fill || st.stroke) {
63824                         colorArrayStyle.push(st.fill || st.stroke);
63825                     }
63826                 }
63827                 if (colorArrayStyle.length) {
63828                     config.colorArrayStyle = colorArrayStyle;
63829                 }
63830             }
63831             config.seriesIdx = idx;
63832         }
63833         if (series instanceof Ext.chart.series.Series) {
63834             Ext.apply(series, config);
63835         } else {
63836             Ext.applyIf(config, series);
63837             series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
63838         }
63839         if (series.initialize) {
63840             series.initialize();
63841         }
63842     },
63843
63844     // @private
63845     getMaxGutter: function() {
63846         var me = this,
63847             maxGutter = [0, 0];
63848         me.series.each(function(s) {
63849             var gutter = s.getGutters && s.getGutters() || [0, 0];
63850             maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
63851             maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
63852         });
63853         me.maxGutter = maxGutter;
63854     },
63855
63856     // @private draw axis.
63857     drawAxis: function(axis) {
63858         axis.drawAxis();
63859     },
63860
63861     // @private draw series.
63862     drawCharts: function(series) {
63863         series.triggerafterrender = false;
63864         series.drawSeries();
63865         if (!this.animate) {
63866             series.fireEvent('afterrender');
63867         }
63868     },
63869
63870     // @private remove gently.
63871     destroy: function() {
63872         Ext.destroy(this.surface);
63873         this.bindStore(null);
63874         this.callParent(arguments);
63875     }
63876 });
63877
63878 /**
63879  * @class Ext.chart.Highlight
63880  * A mixin providing highlight functionality for Ext.chart.series.Series.
63881  */
63882 Ext.define('Ext.chart.Highlight', {
63883
63884     /* Begin Definitions */
63885
63886     requires: ['Ext.fx.Anim'],
63887
63888     /* End Definitions */
63889
63890     /**
63891      * Highlight the given series item.
63892      * @param {Boolean/Object} Default's false. Can also be an object width style properties (i.e fill, stroke, radius) 
63893      * or just use default styles per series by setting highlight = true.
63894      */
63895     highlight: false,
63896
63897     highlightCfg : null,
63898
63899     constructor: function(config) {
63900         if (config.highlight) {
63901             if (config.highlight !== true) { //is an object
63902                 this.highlightCfg = Ext.apply({}, config.highlight);
63903             }
63904             else {
63905                 this.highlightCfg = {
63906                     fill: '#fdd',
63907                     radius: 20,
63908                     lineWidth: 5,
63909                     stroke: '#f55'
63910                 };
63911             }
63912         }
63913     },
63914
63915     /**
63916      * Highlight the given series item.
63917      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
63918      */
63919     highlightItem: function(item) {
63920         if (!item) {
63921             return;
63922         }
63923         
63924         var me = this,
63925             sprite = item.sprite,
63926             opts = me.highlightCfg,
63927             surface = me.chart.surface,
63928             animate = me.chart.animate,
63929             p, from, to, pi;
63930
63931         if (!me.highlight || !sprite || sprite._highlighted) {
63932             return;
63933         }
63934         if (sprite._anim) {
63935             sprite._anim.paused = true;
63936         }
63937         sprite._highlighted = true;
63938         if (!sprite._defaults) {
63939             sprite._defaults = Ext.apply({}, sprite.attr);
63940             from = {};
63941             to = {};
63942             for (p in opts) {
63943                 if (! (p in sprite._defaults)) {
63944                     sprite._defaults[p] = surface.availableAttrs[p];
63945                 }
63946                 from[p] = sprite._defaults[p];
63947                 to[p] = opts[p];
63948                 if (Ext.isObject(opts[p])) {
63949                     from[p] = {};
63950                     to[p] = {};
63951                     Ext.apply(sprite._defaults[p], sprite.attr[p]);
63952                     Ext.apply(from[p], sprite._defaults[p]);
63953                     for (pi in sprite._defaults[p]) {
63954                         if (! (pi in opts[p])) {
63955                             to[p][pi] = from[p][pi];
63956                         } else {
63957                             to[p][pi] = opts[p][pi];
63958                         }
63959                     }
63960                     for (pi in opts[p]) {
63961                         if (! (pi in to[p])) {
63962                             to[p][pi] = opts[p][pi];
63963                         }
63964                     }
63965                 }
63966             }
63967             sprite._from = from;
63968             sprite._to = to;
63969             sprite._endStyle = to;
63970         }
63971         if (animate) {
63972             sprite._anim = Ext.create('Ext.fx.Anim', {
63973                 target: sprite,
63974                 from: sprite._from,
63975                 to: sprite._to,
63976                 duration: 150
63977             });
63978         } else {
63979             sprite.setAttributes(sprite._to, true);
63980         }
63981     },
63982
63983     /**
63984      * Un-highlight any existing highlights
63985      */
63986     unHighlightItem: function() {
63987         if (!this.highlight || !this.items) {
63988             return;
63989         }
63990
63991         var me = this,
63992             items = me.items,
63993             len = items.length,
63994             opts = me.highlightCfg,
63995             animate = me.chart.animate,
63996             i = 0,
63997             obj, p, sprite;
63998
63999         for (; i < len; i++) {
64000             if (!items[i]) {
64001                 continue;
64002             }
64003             sprite = items[i].sprite;
64004             if (sprite && sprite._highlighted) {
64005                 if (sprite._anim) {
64006                     sprite._anim.paused = true;
64007                 }
64008                 obj = {};
64009                 for (p in opts) {
64010                     if (Ext.isObject(sprite._defaults[p])) {
64011                         obj[p] = {};
64012                         Ext.apply(obj[p], sprite._defaults[p]);
64013                     }
64014                     else {
64015                         obj[p] = sprite._defaults[p];
64016                     }
64017                 }
64018                 if (animate) {
64019                     //sprite._to = obj;
64020                     sprite._endStyle = obj;
64021                     sprite._anim = Ext.create('Ext.fx.Anim', {
64022                         target: sprite,
64023                         to: obj,
64024                         duration: 150
64025                     });
64026                 }
64027                 else {
64028                     sprite.setAttributes(obj, true);
64029                 }
64030                 delete sprite._highlighted;
64031                 //delete sprite._defaults;
64032             }
64033         }
64034     },
64035
64036     cleanHighlights: function() {
64037         if (!this.highlight) {
64038             return;
64039         }
64040
64041         var group = this.group,
64042             markerGroup = this.markerGroup,
64043             i = 0,
64044             l;
64045         for (l = group.getCount(); i < l; i++) {
64046             delete group.getAt(i)._defaults;
64047         }
64048         if (markerGroup) {
64049             for (l = markerGroup.getCount(); i < l; i++) {
64050                 delete markerGroup.getAt(i)._defaults;
64051             }
64052         }
64053     }
64054 });
64055 /**
64056  * @class Ext.chart.Label
64057  *
64058  * Labels is a mixin to the Series class. Labels methods are implemented
64059  * in each of the Series (Pie, Bar, etc) for label creation and placement.
64060  *
64061  * The methods implemented by the Series are:
64062  *
64063  * - **`onCreateLabel(storeItem, item, i, display)`** Called each time a new label is created.
64064  *   The arguments of the method are:
64065  *   - *`storeItem`* The element of the store that is related to the label sprite.
64066  *   - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
64067  *     used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
64068  *   - *`i`* The index of the element created (i.e the first created label, second created label, etc)
64069  *   - *`display`* The display type. May be <b>false</b> if the label is hidden
64070  *
64071  *  - **`onPlaceLabel(label, storeItem, item, i, display, animate)`** Called for updating the position of the label.
64072  *    The arguments of the method are:
64073  *    - *`label`* The sprite label.</li>
64074  *    - *`storeItem`* The element of the store that is related to the label sprite</li>
64075  *    - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
64076  *      used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
64077  *    - *`i`* The index of the element to be updated (i.e. whether it is the first, second, third from the labelGroup)
64078  *    - *`display`* The display type. May be <b>false</b> if the label is hidden.
64079  *    - *`animate`* A boolean value to set or unset animations for the labels.
64080  */
64081 Ext.define('Ext.chart.Label', {
64082
64083     /* Begin Definitions */
64084
64085     requires: ['Ext.draw.Color'],
64086
64087     /* End Definitions */
64088
64089     /**
64090      * @cfg {Object} label
64091      * Object with the following properties:
64092      *
64093      * - **display** : String
64094      *
64095      *   Specifies the presence and position of labels for each pie slice. Either "rotate", "middle", "insideStart",
64096      *   "insideEnd", "outside", "over", "under", or "none" to prevent label rendering.
64097      *   Default value: 'none'.
64098      *
64099      * - **color** : String
64100      *
64101      *   The color of the label text.
64102      *   Default value: '#000' (black).
64103      *
64104      * - **contrast** : Boolean
64105      *
64106      *   True to render the label in contrasting color with the backround.
64107      *   Default value: false.
64108      *
64109      * - **field** : String
64110      *
64111      *   The name of the field to be displayed in the label.
64112      *   Default value: 'name'.
64113      *
64114      * - **minMargin** : Number
64115      *
64116      *   Specifies the minimum distance from a label to the origin of the visualization.
64117      *   This parameter is useful when using PieSeries width variable pie slice lengths.
64118      *   Default value: 50.
64119      *
64120      * - **font** : String
64121      *
64122      *   The font used for the labels.
64123      *   Default value: "11px Helvetica, sans-serif".
64124      *
64125      * - **orientation** : String
64126      *
64127      *   Either "horizontal" or "vertical".
64128      *   Dafault value: "horizontal".
64129      *
64130      * - **renderer** : Function
64131      *
64132      *   Optional function for formatting the label into a displayable value.
64133      *   Default value: function(v) { return v; }
64134      */
64135
64136     //@private a regex to parse url type colors.
64137     colorStringRe: /url\s*\(\s*#([^\/)]+)\s*\)/,
64138
64139     //@private the mixin constructor. Used internally by Series.
64140     constructor: function(config) {
64141         var me = this;
64142         me.label = Ext.applyIf(me.label || {},
64143         {
64144             display: "none",
64145             color: "#000",
64146             field: "name",
64147             minMargin: 50,
64148             font: "11px Helvetica, sans-serif",
64149             orientation: "horizontal",
64150             renderer: function(v) {
64151                 return v;
64152             }
64153         });
64154
64155         if (me.label.display !== 'none') {
64156             me.labelsGroup = me.chart.surface.getGroup(me.seriesId + '-labels');
64157         }
64158     },
64159
64160     //@private a method to render all labels in the labelGroup
64161     renderLabels: function() {
64162         var me = this,
64163             chart = me.chart,
64164             gradients = chart.gradients,
64165             items = me.items,
64166             animate = chart.animate,
64167             config = me.label,
64168             display = config.display,
64169             color = config.color,
64170             field = [].concat(config.field),
64171             group = me.labelsGroup,
64172             groupLength = (group || 0) && group.length,
64173             store = me.chart.store,
64174             len = store.getCount(),
64175             itemLength = (items || 0) && items.length,
64176             ratio = itemLength / len,
64177             gradientsCount = (gradients || 0) && gradients.length,
64178             Color = Ext.draw.Color,
64179             hides = [],
64180             gradient, i, count, groupIndex, index, j, k, colorStopTotal, colorStopIndex, colorStop, item, label,
64181             storeItem, sprite, spriteColor, spriteBrightness, labelColor, colorString;
64182
64183         if (display == 'none') {
64184             return;
64185         }
64186         // no items displayed, hide all labels
64187         if(itemLength == 0){
64188             while(groupLength--)
64189                 hides.push(groupLength);
64190         }else{
64191             for (i = 0, count = 0, groupIndex = 0; i < len; i++) {
64192                 index = 0;
64193                 for (j = 0; j < ratio; j++) {
64194                     item = items[count];
64195                     label = group.getAt(groupIndex);
64196                     storeItem = store.getAt(i);
64197                     //check the excludes
64198                     while(this.__excludes && this.__excludes[index] && ratio > 1) {
64199                         if(field[j]){
64200                             hides.push(groupIndex);
64201                         }
64202                         index++;
64203
64204                     }
64205
64206                     if (!item && label) {
64207                         label.hide(true);
64208                         groupIndex++;
64209                     }
64210
64211                     if (item && field[j]) {
64212                         if (!label) {
64213                             label = me.onCreateLabel(storeItem, item, i, display, j, index);
64214                         }
64215                         me.onPlaceLabel(label, storeItem, item, i, display, animate, j, index);
64216                         groupIndex++;
64217
64218                         //set contrast
64219                         if (config.contrast && item.sprite) {
64220                             sprite = item.sprite;
64221                             //set the color string to the color to be set.
64222                             if (sprite._endStyle) {
64223                                 colorString = sprite._endStyle.fill;
64224                             }
64225                             else if (sprite._to) {
64226                                 colorString = sprite._to.fill;
64227                             }
64228                             else {
64229                                 colorString = sprite.attr.fill;
64230                             }
64231                             colorString = colorString || sprite.attr.fill;
64232
64233                             spriteColor = Color.fromString(colorString);
64234                             //color wasn't parsed property maybe because it's a gradient id
64235                             if (colorString && !spriteColor) {
64236                                 colorString = colorString.match(me.colorStringRe)[1];
64237                                 for (k = 0; k < gradientsCount; k++) {
64238                                     gradient = gradients[k];
64239                                     if (gradient.id == colorString) {
64240                                         //avg color stops
64241                                         colorStop = 0; colorStopTotal = 0;
64242                                         for (colorStopIndex in gradient.stops) {
64243                                             colorStop++;
64244                                             colorStopTotal += Color.fromString(gradient.stops[colorStopIndex].color).getGrayscale();
64245                                         }
64246                                         spriteBrightness = (colorStopTotal / colorStop) / 255;
64247                                         break;
64248                                     }
64249                                 }
64250                             }
64251                             else {
64252                                 spriteBrightness = spriteColor.getGrayscale() / 255;
64253                             }
64254                             if (label.isOutside) {
64255                                 spriteBrightness = 1;
64256                             }
64257                             labelColor = Color.fromString(label.attr.color || label.attr.fill).getHSL();
64258                             labelColor[2] = spriteBrightness > 0.5 ? 0.2 : 0.8;
64259                             label.setAttributes({
64260                                 fill: String(Color.fromHSL.apply({}, labelColor))
64261                             }, true);
64262                         }
64263
64264                     }
64265                     count++;
64266                     index++;
64267                 }
64268             }
64269         }
64270         me.hideLabels(hides);
64271     },
64272     hideLabels: function(hides){
64273         var labelsGroup = this.labelsGroup,
64274             hlen = hides.length;
64275         while(hlen--)
64276             labelsGroup.getAt(hides[hlen]).hide(true);
64277     }
64278 });
64279 Ext.define('Ext.chart.MaskLayer', {
64280     extend: 'Ext.Component',
64281     
64282     constructor: function(config) {
64283         config = Ext.apply(config || {}, {
64284             style: 'position:absolute;background-color:#888;cursor:move;opacity:0.6;border:1px solid #222;'
64285         });
64286         this.callParent([config]);    
64287     },
64288     
64289     initComponent: function() {
64290         var me = this;
64291         me.callParent(arguments);
64292         me.addEvents(
64293             'mousedown',
64294             'mouseup',
64295             'mousemove',
64296             'mouseenter',
64297             'mouseleave'
64298         );
64299     },
64300
64301     initDraggable: function() {
64302         this.callParent(arguments);
64303         this.dd.onStart = function (e) {
64304             var me = this,
64305                 comp = me.comp;
64306     
64307             // Cache the start [X, Y] array
64308             this.startPosition = comp.getPosition(true);
64309     
64310             // If client Component has a ghost method to show a lightweight version of itself
64311             // then use that as a drag proxy unless configured to liveDrag.
64312             if (comp.ghost && !comp.liveDrag) {
64313                  me.proxy = comp.ghost();
64314                  me.dragTarget = me.proxy.header.el;
64315             }
64316     
64317             // Set the constrainTo Region before we start dragging.
64318             if (me.constrain || me.constrainDelegate) {
64319                 me.constrainTo = me.calculateConstrainRegion();
64320             }
64321         };
64322     }
64323 });
64324 /**
64325  * @class Ext.chart.TipSurface
64326  * @ignore
64327  */
64328 Ext.define('Ext.chart.TipSurface', {
64329
64330     /* Begin Definitions */
64331
64332     extend: 'Ext.draw.Component',
64333
64334     /* End Definitions */
64335
64336     spriteArray: false,
64337     renderFirst: true,
64338
64339     constructor: function(config) {
64340         this.callParent([config]);
64341         if (config.sprites) {
64342             this.spriteArray = [].concat(config.sprites);
64343             delete config.sprites;
64344         }
64345     },
64346
64347     onRender: function() {
64348         var me = this,
64349             i = 0,
64350             l = 0,
64351             sp,
64352             sprites;
64353             this.callParent(arguments);
64354         sprites = me.spriteArray;
64355         if (me.renderFirst && sprites) {
64356             me.renderFirst = false;
64357             for (l = sprites.length; i < l; i++) {
64358                 sp = me.surface.add(sprites[i]);
64359                 sp.setAttributes({
64360                     hidden: false
64361                 },
64362                 true);
64363             }
64364         }
64365     }
64366 });
64367
64368 /**
64369  * @class Ext.chart.Tip
64370  * Provides tips for Ext.chart.series.Series.
64371  */
64372 Ext.define('Ext.chart.Tip', {
64373
64374     /* Begin Definitions */
64375
64376     requires: ['Ext.tip.ToolTip', 'Ext.chart.TipSurface'],
64377
64378     /* End Definitions */
64379
64380     constructor: function(config) {
64381         var me = this,
64382             surface,
64383             sprites,
64384             tipSurface;
64385         if (config.tips) {
64386             me.tipTimeout = null;
64387             me.tipConfig = Ext.apply({}, config.tips, {
64388                 renderer: Ext.emptyFn,
64389                 constrainPosition: false
64390             });
64391             me.tooltip = Ext.create('Ext.tip.ToolTip', me.tipConfig);
64392             me.chart.surface.on('mousemove', me.tooltip.onMouseMove, me.tooltip);
64393             me.chart.surface.on('mouseleave', function() {
64394                 me.hideTip();
64395             });
64396             if (me.tipConfig.surface) {
64397                 //initialize a surface
64398                 surface = me.tipConfig.surface;
64399                 sprites = surface.sprites;
64400                 tipSurface = Ext.create('Ext.chart.TipSurface', {
64401                     id: 'tipSurfaceComponent',
64402                     sprites: sprites
64403                 });
64404                 if (surface.width && surface.height) {
64405                     tipSurface.setSize(surface.width, surface.height);
64406                 }
64407                 me.tooltip.add(tipSurface);
64408                 me.spriteTip = tipSurface;
64409             }
64410         }
64411     },
64412
64413     showTip: function(item) {
64414         var me = this;
64415         if (!me.tooltip) {
64416             return;
64417         }
64418         clearTimeout(me.tipTimeout);
64419         var tooltip = me.tooltip,
64420             spriteTip = me.spriteTip,
64421             tipConfig = me.tipConfig,
64422             trackMouse = tooltip.trackMouse,
64423             sprite, surface, surfaceExt, pos, x, y;
64424         if (!trackMouse) {
64425             tooltip.trackMouse = true;
64426             sprite = item.sprite;
64427             surface = sprite.surface;
64428             surfaceExt = Ext.get(surface.getId());
64429             if (surfaceExt) {
64430                 pos = surfaceExt.getXY();
64431                 x = pos[0] + (sprite.attr.x || 0) + (sprite.attr.translation && sprite.attr.translation.x || 0);
64432                 y = pos[1] + (sprite.attr.y || 0) + (sprite.attr.translation && sprite.attr.translation.y || 0);
64433                 tooltip.targetXY = [x, y];
64434             }
64435         }
64436         if (spriteTip) {
64437             tipConfig.renderer.call(tooltip, item.storeItem, item, spriteTip.surface);
64438         } else {
64439             tipConfig.renderer.call(tooltip, item.storeItem, item);
64440         }
64441         tooltip.show();
64442         tooltip.trackMouse = trackMouse;
64443     },
64444
64445     hideTip: function(item) {
64446         var tooltip = this.tooltip;
64447         if (!tooltip) {
64448             return;
64449         }
64450         clearTimeout(this.tipTimeout);
64451         this.tipTimeout = setTimeout(function() {
64452             tooltip.hide();
64453         }, 0);
64454     }
64455 });
64456 /**
64457  * @class Ext.chart.axis.Abstract
64458  * Base class for all axis classes.
64459  * @private
64460  */
64461 Ext.define('Ext.chart.axis.Abstract', {
64462
64463     /* Begin Definitions */
64464
64465     requires: ['Ext.chart.Chart'],
64466
64467     /* End Definitions */
64468
64469     /**
64470      * Creates new Axis.
64471      * @param {Object} config (optional) Config options.
64472      */
64473     constructor: function(config) {
64474         config = config || {};
64475
64476         var me = this,
64477             pos = config.position || 'left';
64478
64479         pos = pos.charAt(0).toUpperCase() + pos.substring(1);
64480         //axisLabel(Top|Bottom|Right|Left)Style
64481         config.label = Ext.apply(config['axisLabel' + pos + 'Style'] || {}, config.label || {});
64482         config.axisTitleStyle = Ext.apply(config['axisTitle' + pos + 'Style'] || {}, config.labelTitle || {});
64483         Ext.apply(me, config);
64484         me.fields = [].concat(me.fields);
64485         this.callParent();
64486         me.labels = [];
64487         me.getId();
64488         me.labelGroup = me.chart.surface.getGroup(me.axisId + "-labels");
64489     },
64490
64491     alignment: null,
64492     grid: false,
64493     steps: 10,
64494     x: 0,
64495     y: 0,
64496     minValue: 0,
64497     maxValue: 0,
64498
64499     getId: function() {
64500         return this.axisId || (this.axisId = Ext.id(null, 'ext-axis-'));
64501     },
64502
64503     /*
64504       Called to process a view i.e to make aggregation and filtering over
64505       a store creating a substore to be used to render the axis. Since many axes
64506       may do different things on the data and we want the final result of all these
64507       operations to be rendered we need to call processView on all axes before drawing
64508       them.
64509     */
64510     processView: Ext.emptyFn,
64511
64512     drawAxis: Ext.emptyFn,
64513     addDisplayAndLabels: Ext.emptyFn
64514 });
64515
64516 /**
64517  * @class Ext.chart.axis.Axis
64518  * @extends Ext.chart.axis.Abstract
64519  *
64520  * Defines axis for charts. The axis position, type, style can be configured.
64521  * The axes are defined in an axes array of configuration objects where the type,
64522  * field, grid and other configuration options can be set. To know more about how
64523  * to create a Chart please check the Chart class documentation. Here's an example for the axes part:
64524  * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
64525  *
64526  *     axes: [{
64527  *         type: 'Numeric',
64528  *         grid: true,
64529  *         position: 'left',
64530  *         fields: ['data1', 'data2', 'data3'],
64531  *         title: 'Number of Hits',
64532  *         grid: {
64533  *             odd: {
64534  *                 opacity: 1,
64535  *                 fill: '#ddd',
64536  *                 stroke: '#bbb',
64537  *                 'stroke-width': 1
64538  *             }
64539  *         },
64540  *         minimum: 0
64541  *     }, {
64542  *         type: 'Category',
64543  *         position: 'bottom',
64544  *         fields: ['name'],
64545  *         title: 'Month of the Year',
64546  *         grid: true,
64547  *         label: {
64548  *             rotate: {
64549  *                 degrees: 315
64550  *             }
64551  *         }
64552  *     }]
64553  *
64554  * 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
64555  * 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.
64556  * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
64557  * category axis the labels will be rotated so they can fit the space better.
64558  */
64559 Ext.define('Ext.chart.axis.Axis', {
64560
64561     /* Begin Definitions */
64562
64563     extend: 'Ext.chart.axis.Abstract',
64564
64565     alternateClassName: 'Ext.chart.Axis',
64566
64567     requires: ['Ext.draw.Draw'],
64568
64569     /* End Definitions */
64570
64571     /**
64572      * @cfg {Boolean/Object} grid
64573      * The grid configuration enables you to set a background grid for an axis.
64574      * If set to *true* on a vertical axis, vertical lines will be drawn.
64575      * If set to *true* on a horizontal axis, horizontal lines will be drawn.
64576      * If both are set, a proper grid with horizontal and vertical lines will be drawn.
64577      *
64578      * You can set specific options for the grid configuration for odd and/or even lines/rows.
64579      * Since the rows being drawn are rectangle sprites, you can set to an odd or even property
64580      * all styles that apply to {@link Ext.draw.Sprite}. For more information on all the style
64581      * properties you can set please take a look at {@link Ext.draw.Sprite}. Some useful style properties are `opacity`, `fill`, `stroke`, `stroke-width`, etc.
64582      *
64583      * The possible values for a grid option are then *true*, *false*, or an object with `{ odd, even }` properties
64584      * where each property contains a sprite style descriptor object that is defined in {@link Ext.draw.Sprite}.
64585      *
64586      * For example:
64587      *
64588      *     axes: [{
64589      *         type: 'Numeric',
64590      *         grid: true,
64591      *         position: 'left',
64592      *         fields: ['data1', 'data2', 'data3'],
64593      *         title: 'Number of Hits',
64594      *         grid: {
64595      *             odd: {
64596      *                 opacity: 1,
64597      *                 fill: '#ddd',
64598      *                 stroke: '#bbb',
64599      *                 'stroke-width': 1
64600      *             }
64601      *         }
64602      *     }, {
64603      *         type: 'Category',
64604      *         position: 'bottom',
64605      *         fields: ['name'],
64606      *         title: 'Month of the Year',
64607      *         grid: true
64608      *     }]
64609      *
64610      */
64611
64612     /**
64613      * @cfg {Number} majorTickSteps
64614      * If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
64615      */
64616
64617     /**
64618      * @cfg {Number} minorTickSteps
64619      * The number of small ticks between two major ticks. Default is zero.
64620      */
64621
64622     /**
64623      * @cfg {String} title
64624      * The title for the Axis
64625      */
64626
64627     //@private force min/max values from store
64628     forceMinMax: false,
64629
64630     /**
64631      * @cfg {Number} dashSize
64632      * The size of the dash marker. Default's 3.
64633      */
64634     dashSize: 3,
64635
64636     /**
64637      * @cfg {String} position
64638      * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
64639      */
64640     position: 'bottom',
64641
64642     // @private
64643     skipFirst: false,
64644
64645     /**
64646      * @cfg {Number} length
64647      * Offset axis position. Default's 0.
64648      */
64649     length: 0,
64650
64651     /**
64652      * @cfg {Number} width
64653      * Offset axis width. Default's 0.
64654      */
64655     width: 0,
64656
64657     majorTickSteps: false,
64658
64659     // @private
64660     applyData: Ext.emptyFn,
64661
64662     getRange: function () {
64663         var me = this,
64664             store = me.chart.getChartStore(),
64665             fields = me.fields,
64666             ln = fields.length,
64667             math = Math,
64668             mmax = math.max,
64669             mmin = math.min,
64670             aggregate = false,
64671             min = isNaN(me.minimum) ? Infinity : me.minimum,
64672             max = isNaN(me.maximum) ? -Infinity : me.maximum,
64673             total = 0, i, l, value, values, rec,
64674             excludes = [],
64675             series = me.chart.series.items;
64676
64677         //if one series is stacked I have to aggregate the values
64678         //for the scale.
64679         // TODO(zhangbei): the code below does not support series that stack on 1 side but non-stacked axis
64680         // listed in axis config. For example, a Area series whose axis : ['left', 'bottom'].
64681         // Assuming only stack on y-axis.
64682         // CHANGED BY Nicolas: I removed the check `me.position == 'left'` and `me.position == 'right'` since 
64683         // it was constraining the minmax calculation to y-axis stacked
64684         // visualizations.
64685         for (i = 0, l = series.length; !aggregate && i < l; i++) {
64686             aggregate = aggregate || series[i].stacked;
64687             excludes = series[i].__excludes || excludes;
64688         }
64689         store.each(function(record) {
64690             if (aggregate) {
64691                 if (!isFinite(min)) {
64692                     min = 0;
64693                 }
64694                 for (values = [0, 0], i = 0; i < ln; i++) {
64695                     if (excludes[i]) {
64696                         continue;
64697                     }
64698                     rec = record.get(fields[i]);
64699                     values[+(rec > 0)] += math.abs(rec);
64700                 }
64701                 max = mmax(max, -values[0], +values[1]);
64702                 min = mmin(min, -values[0], +values[1]);
64703             }
64704             else {
64705                 for (i = 0; i < ln; i++) {
64706                     if (excludes[i]) {
64707                         continue;
64708                     }
64709                     value = record.get(fields[i]);
64710                     max = mmax(max, +value);
64711                     min = mmin(min, +value);
64712                 }
64713             }
64714         });
64715         if (!isFinite(max)) {
64716             max = me.prevMax || 0;
64717         }
64718         if (!isFinite(min)) {
64719             min = me.prevMin || 0;
64720         }
64721         //normalize min max for snapEnds.
64722         if (min != max && (max != Math.floor(max))) {
64723             max = Math.floor(max) + 1;
64724         }
64725
64726         if (!isNaN(me.minimum)) {
64727             min = me.minimum;
64728         }
64729         
64730         if (!isNaN(me.maximum)) {
64731             max = me.maximum;
64732         }
64733
64734         return {min: min, max: max};
64735     },
64736
64737     // @private creates a structure with start, end and step points.
64738     calcEnds: function() {
64739         var me = this,
64740             fields = me.fields,
64741             range = me.getRange(),
64742             min = range.min,
64743             max = range.max,
64744             outfrom, outto, out;
64745
64746         out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ?  (me.majorTickSteps +1) : me.steps);
64747         outfrom = out.from;
64748         outto = out.to;
64749         if (me.forceMinMax) {
64750             if (!isNaN(max)) {
64751                 out.to = max;
64752             }
64753             if (!isNaN(min)) {
64754                 out.from = min;
64755             }
64756         }
64757         if (!isNaN(me.maximum)) {
64758             //TODO(nico) users are responsible for their own minimum/maximum values set.
64759             //Clipping should be added to remove lines in the chart which are below the axis.
64760             out.to = me.maximum;
64761         }
64762         if (!isNaN(me.minimum)) {
64763             //TODO(nico) users are responsible for their own minimum/maximum values set.
64764             //Clipping should be added to remove lines in the chart which are below the axis.
64765             out.from = me.minimum;
64766         }
64767
64768         //Adjust after adjusting minimum and maximum
64769         out.step = (out.to - out.from) / (outto - outfrom) * out.step;
64770
64771         if (me.adjustMaximumByMajorUnit) {
64772             out.to += out.step;
64773         }
64774         if (me.adjustMinimumByMajorUnit) {
64775             out.from -= out.step;
64776         }
64777         me.prevMin = min == max? 0 : min;
64778         me.prevMax = max;
64779         return out;
64780     },
64781
64782     /**
64783      * Renders the axis into the screen and updates its position.
64784      */
64785     drawAxis: function (init) {
64786         var me = this,
64787             i, j,
64788             x = me.x,
64789             y = me.y,
64790             gutterX = me.chart.maxGutter[0],
64791             gutterY = me.chart.maxGutter[1],
64792             dashSize = me.dashSize,
64793             subDashesX = me.minorTickSteps || 0,
64794             subDashesY = me.minorTickSteps || 0,
64795             length = me.length,
64796             position = me.position,
64797             inflections = [],
64798             calcLabels = false,
64799             stepCalcs = me.applyData(),
64800             step = stepCalcs.step,
64801             steps = stepCalcs.steps,
64802             from = stepCalcs.from,
64803             to = stepCalcs.to,
64804             trueLength,
64805             currentX,
64806             currentY,
64807             path,
64808             prev,
64809             dashesX,
64810             dashesY,
64811             delta;
64812
64813         //If no steps are specified
64814         //then don't draw the axis. This generally happens
64815         //when an empty store.
64816         if (me.hidden || isNaN(step) || (from == to)) {
64817             return;
64818         }
64819
64820         me.from = stepCalcs.from;
64821         me.to = stepCalcs.to;
64822         if (position == 'left' || position == 'right') {
64823             currentX = Math.floor(x) + 0.5;
64824             path = ["M", currentX, y, "l", 0, -length];
64825             trueLength = length - (gutterY * 2);
64826         }
64827         else {
64828             currentY = Math.floor(y) + 0.5;
64829             path = ["M", x, currentY, "l", length, 0];
64830             trueLength = length - (gutterX * 2);
64831         }
64832
64833         delta = trueLength / (steps || 1);
64834         dashesX = Math.max(subDashesX +1, 0);
64835         dashesY = Math.max(subDashesY +1, 0);
64836         if (me.type == 'Numeric' || me.type == 'Time') {
64837             calcLabels = true;
64838             me.labels = [stepCalcs.from];
64839         }
64840         if (position == 'right' || position == 'left') {
64841             currentY = y - gutterY;
64842             currentX = x - ((position == 'left') * dashSize * 2);
64843             while (currentY >= y - gutterY - trueLength) {
64844                 path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
64845                 if (currentY != y - gutterY) {
64846                     for (i = 1; i < dashesY; i++) {
64847                         path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
64848                     }
64849                 }
64850                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
64851                 currentY -= delta;
64852                 if (calcLabels) {
64853                     me.labels.push(me.labels[me.labels.length -1] + step);
64854                 }
64855                 if (delta === 0) {
64856                     break;
64857                 }
64858             }
64859             if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
64860                 path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
64861                 for (i = 1; i < dashesY; i++) {
64862                     path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
64863                 }
64864                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
64865                 if (calcLabels) {
64866                     me.labels.push(me.labels[me.labels.length -1] + step);
64867                 }
64868             }
64869         } else {
64870             currentX = x + gutterX;
64871             currentY = y - ((position == 'top') * dashSize * 2);
64872             while (currentX <= x + gutterX + trueLength) {
64873                 path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
64874                 if (currentX != x + gutterX) {
64875                     for (i = 1; i < dashesX; i++) {
64876                         path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
64877                     }
64878                 }
64879                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
64880                 currentX += delta;
64881                 if (calcLabels) {
64882                     me.labels.push(me.labels[me.labels.length -1] + step);
64883                 }
64884                 if (delta === 0) {
64885                     break;
64886                 }
64887             }
64888             if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
64889                 path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
64890                 for (i = 1; i < dashesX; i++) {
64891                     path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
64892                 }
64893                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
64894                 if (calcLabels) {
64895                     me.labels.push(me.labels[me.labels.length -1] + step);
64896                 }
64897             }
64898         }
64899         if (!me.axis) {
64900             me.axis = me.chart.surface.add(Ext.apply({
64901                 type: 'path',
64902                 path: path
64903             }, me.axisStyle));
64904         }
64905         me.axis.setAttributes({
64906             path: path
64907         }, true);
64908         me.inflections = inflections;
64909         if (!init && me.grid) {
64910             me.drawGrid();
64911         }
64912         me.axisBBox = me.axis.getBBox();
64913         me.drawLabel();
64914     },
64915
64916     /**
64917      * Renders an horizontal and/or vertical grid into the Surface.
64918      */
64919     drawGrid: function() {
64920         var me = this,
64921             surface = me.chart.surface,
64922             grid = me.grid,
64923             odd = grid.odd,
64924             even = grid.even,
64925             inflections = me.inflections,
64926             ln = inflections.length - ((odd || even)? 0 : 1),
64927             position = me.position,
64928             gutter = me.chart.maxGutter,
64929             width = me.width - 2,
64930             vert = false,
64931             point, prevPoint,
64932             i = 1,
64933             path = [], styles, lineWidth, dlineWidth,
64934             oddPath = [], evenPath = [];
64935
64936         if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
64937             (gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
64938             i = 0;
64939             ln++;
64940         }
64941         for (; i < ln; i++) {
64942             point = inflections[i];
64943             prevPoint = inflections[i - 1];
64944             if (odd || even) {
64945                 path = (i % 2)? oddPath : evenPath;
64946                 styles = ((i % 2)? odd : even) || {};
64947                 lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
64948                 dlineWidth = 2 * lineWidth;
64949                 if (position == 'left') {
64950                     path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth,
64951                               "L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
64952                               "L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
64953                               "L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
64954                 }
64955                 else if (position == 'right') {
64956                     path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth,
64957                               "L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
64958                               "L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
64959                               "L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
64960                 }
64961                 else if (position == 'top') {
64962                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth,
64963                               "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
64964                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
64965                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
64966                 }
64967                 else {
64968                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth,
64969                             "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
64970                             "L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
64971                             "L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
64972                 }
64973             } else {
64974                 if (position == 'left') {
64975                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]);
64976                 }
64977                 else if (position == 'right') {
64978                     path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]);
64979                 }
64980                 else if (position == 'top') {
64981                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]);
64982                 }
64983                 else {
64984                     path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]);
64985                 }
64986             }
64987         }
64988         if (odd || even) {
64989             if (oddPath.length) {
64990                 if (!me.gridOdd && oddPath.length) {
64991                     me.gridOdd = surface.add({
64992                         type: 'path',
64993                         path: oddPath
64994                     });
64995                 }
64996                 me.gridOdd.setAttributes(Ext.apply({
64997                     path: oddPath,
64998                     hidden: false
64999                 }, odd || {}), true);
65000             }
65001             if (evenPath.length) {
65002                 if (!me.gridEven) {
65003                     me.gridEven = surface.add({
65004                         type: 'path',
65005                         path: evenPath
65006                     });
65007                 }
65008                 me.gridEven.setAttributes(Ext.apply({
65009                     path: evenPath,
65010                     hidden: false
65011                 }, even || {}), true);
65012             }
65013         }
65014         else {
65015             if (path.length) {
65016                 if (!me.gridLines) {
65017                     me.gridLines = me.chart.surface.add({
65018                         type: 'path',
65019                         path: path,
65020                         "stroke-width": me.lineWidth || 1,
65021                         stroke: me.gridColor || '#ccc'
65022                     });
65023                 }
65024                 me.gridLines.setAttributes({
65025                     hidden: false,
65026                     path: path
65027                 }, true);
65028             }
65029             else if (me.gridLines) {
65030                 me.gridLines.hide(true);
65031             }
65032         }
65033     },
65034
65035     //@private
65036     getOrCreateLabel: function(i, text) {
65037         var me = this,
65038             labelGroup = me.labelGroup,
65039             textLabel = labelGroup.getAt(i),
65040             surface = me.chart.surface;
65041         if (textLabel) {
65042             if (text != textLabel.attr.text) {
65043                 textLabel.setAttributes(Ext.apply({
65044                     text: text
65045                 }, me.label), true);
65046                 textLabel._bbox = textLabel.getBBox();
65047             }
65048         }
65049         else {
65050             textLabel = surface.add(Ext.apply({
65051                 group: labelGroup,
65052                 type: 'text',
65053                 x: 0,
65054                 y: 0,
65055                 text: text
65056             }, me.label));
65057             surface.renderItem(textLabel);
65058             textLabel._bbox = textLabel.getBBox();
65059         }
65060         //get untransformed bounding box
65061         if (me.label.rotation) {
65062             textLabel.setAttributes({
65063                 rotation: {
65064                     degrees: 0
65065                 }
65066             }, true);
65067             textLabel._ubbox = textLabel.getBBox();
65068             textLabel.setAttributes(me.label, true);
65069         } else {
65070             textLabel._ubbox = textLabel._bbox;
65071         }
65072         return textLabel;
65073     },
65074
65075     rect2pointArray: function(sprite) {
65076         var surface = this.chart.surface,
65077             rect = surface.getBBox(sprite, true),
65078             p1 = [rect.x, rect.y],
65079             p1p = p1.slice(),
65080             p2 = [rect.x + rect.width, rect.y],
65081             p2p = p2.slice(),
65082             p3 = [rect.x + rect.width, rect.y + rect.height],
65083             p3p = p3.slice(),
65084             p4 = [rect.x, rect.y + rect.height],
65085             p4p = p4.slice(),
65086             matrix = sprite.matrix;
65087         //transform the points
65088         p1[0] = matrix.x.apply(matrix, p1p);
65089         p1[1] = matrix.y.apply(matrix, p1p);
65090
65091         p2[0] = matrix.x.apply(matrix, p2p);
65092         p2[1] = matrix.y.apply(matrix, p2p);
65093
65094         p3[0] = matrix.x.apply(matrix, p3p);
65095         p3[1] = matrix.y.apply(matrix, p3p);
65096
65097         p4[0] = matrix.x.apply(matrix, p4p);
65098         p4[1] = matrix.y.apply(matrix, p4p);
65099         return [p1, p2, p3, p4];
65100     },
65101
65102     intersect: function(l1, l2) {
65103         var r1 = this.rect2pointArray(l1),
65104             r2 = this.rect2pointArray(l2);
65105         return !!Ext.draw.Draw.intersect(r1, r2).length;
65106     },
65107
65108     drawHorizontalLabels: function() {
65109        var  me = this,
65110             labelConf = me.label,
65111             floor = Math.floor,
65112             max = Math.max,
65113             axes = me.chart.axes,
65114             position = me.position,
65115             inflections = me.inflections,
65116             ln = inflections.length,
65117             labels = me.labels,
65118             labelGroup = me.labelGroup,
65119             maxHeight = 0,
65120             ratio,
65121             gutterY = me.chart.maxGutter[1],
65122             ubbox, bbox, point, prevX, prevLabel,
65123             projectedWidth = 0,
65124             textLabel, attr, textRight, text,
65125             label, last, x, y, i, firstLabel;
65126
65127         last = ln - 1;
65128         //get a reference to the first text label dimensions
65129         point = inflections[0];
65130         firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
65131         ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
65132
65133         for (i = 0; i < ln; i++) {
65134             point = inflections[i];
65135             text = me.label.renderer(labels[i]);
65136             textLabel = me.getOrCreateLabel(i, text);
65137             bbox = textLabel._bbox;
65138             maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
65139             x = floor(point[0] - (ratio? bbox.height : bbox.width) / 2);
65140             if (me.chart.maxGutter[0] == 0) {
65141                 if (i == 0 && axes.findIndex('position', 'left') == -1) {
65142                     x = point[0];
65143                 }
65144                 else if (i == last && axes.findIndex('position', 'right') == -1) {
65145                     x = point[0] - bbox.width;
65146                 }
65147             }
65148             if (position == 'top') {
65149                 y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
65150             }
65151             else {
65152                 y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
65153             }
65154
65155             textLabel.setAttributes({
65156                 hidden: false,
65157                 x: x,
65158                 y: y
65159             }, true);
65160
65161             // Skip label if there isn't available minimum space
65162             if (i != 0 && (me.intersect(textLabel, prevLabel)
65163                 || me.intersect(textLabel, firstLabel))) {
65164                 textLabel.hide(true);
65165                 continue;
65166             }
65167
65168             prevLabel = textLabel;
65169         }
65170
65171         return maxHeight;
65172     },
65173
65174     drawVerticalLabels: function() {
65175         var me = this,
65176             inflections = me.inflections,
65177             position = me.position,
65178             ln = inflections.length,
65179             labels = me.labels,
65180             maxWidth = 0,
65181             max = Math.max,
65182             floor = Math.floor,
65183             ceil = Math.ceil,
65184             axes = me.chart.axes,
65185             gutterY = me.chart.maxGutter[1],
65186             ubbox, bbox, point, prevLabel,
65187             projectedWidth = 0,
65188             textLabel, attr, textRight, text,
65189             label, last, x, y, i;
65190
65191         last = ln;
65192         for (i = 0; i < last; i++) {
65193             point = inflections[i];
65194             text = me.label.renderer(labels[i]);
65195             textLabel = me.getOrCreateLabel(i, text);
65196             bbox = textLabel._bbox;
65197
65198             maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
65199             y = point[1];
65200             if (gutterY < bbox.height / 2) {
65201                 if (i == last - 1 && axes.findIndex('position', 'top') == -1) {
65202                     y = me.y - me.length + ceil(bbox.height / 2);
65203                 }
65204                 else if (i == 0 && axes.findIndex('position', 'bottom') == -1) {
65205                     y = me.y - floor(bbox.height / 2);
65206                 }
65207             }
65208             if (position == 'left') {
65209                 x = point[0] - bbox.width - me.dashSize - me.label.padding - 2;
65210             }
65211             else {
65212                 x = point[0] + me.dashSize + me.label.padding + 2;
65213             }
65214             textLabel.setAttributes(Ext.apply({
65215                 hidden: false,
65216                 x: x,
65217                 y: y
65218             }, me.label), true);
65219             // Skip label if there isn't available minimum space
65220             if (i != 0 && me.intersect(textLabel, prevLabel)) {
65221                 textLabel.hide(true);
65222                 continue;
65223             }
65224             prevLabel = textLabel;
65225         }
65226
65227         return maxWidth;
65228     },
65229
65230     /**
65231      * Renders the labels in the axes.
65232      */
65233     drawLabel: function() {
65234         var me = this,
65235             position = me.position,
65236             labelGroup = me.labelGroup,
65237             inflections = me.inflections,
65238             maxWidth = 0,
65239             maxHeight = 0,
65240             ln, i;
65241
65242         if (position == 'left' || position == 'right') {
65243             maxWidth = me.drawVerticalLabels();
65244         } else {
65245             maxHeight = me.drawHorizontalLabels();
65246         }
65247
65248         // Hide unused bars
65249         ln = labelGroup.getCount();
65250         i = inflections.length;
65251         for (; i < ln; i++) {
65252             labelGroup.getAt(i).hide(true);
65253         }
65254
65255         me.bbox = {};
65256         Ext.apply(me.bbox, me.axisBBox);
65257         me.bbox.height = maxHeight;
65258         me.bbox.width = maxWidth;
65259         if (Ext.isString(me.title)) {
65260             me.drawTitle(maxWidth, maxHeight);
65261         }
65262     },
65263
65264     // @private creates the elipsis for the text.
65265     elipsis: function(sprite, text, desiredWidth, minWidth, center) {
65266         var bbox,
65267             x;
65268
65269         if (desiredWidth < minWidth) {
65270             sprite.hide(true);
65271             return false;
65272         }
65273         while (text.length > 4) {
65274             text = text.substr(0, text.length - 4) + "...";
65275             sprite.setAttributes({
65276                 text: text
65277             }, true);
65278             bbox = sprite.getBBox();
65279             if (bbox.width < desiredWidth) {
65280                 if (typeof center == 'number') {
65281                     sprite.setAttributes({
65282                         x: Math.floor(center - (bbox.width / 2))
65283                     }, true);
65284                 }
65285                 break;
65286             }
65287         }
65288         return true;
65289     },
65290
65291     /**
65292      * Updates the {@link #title} of this axis.
65293      * @param {String} title
65294      */
65295     setTitle: function(title) {
65296         this.title = title;
65297         this.drawLabel();
65298     },
65299
65300     // @private draws the title for the axis.
65301     drawTitle: function(maxWidth, maxHeight) {
65302         var me = this,
65303             position = me.position,
65304             surface = me.chart.surface,
65305             displaySprite = me.displaySprite,
65306             title = me.title,
65307             rotate = (position == 'left' || position == 'right'),
65308             x = me.x,
65309             y = me.y,
65310             base, bbox, pad;
65311
65312         if (displaySprite) {
65313             displaySprite.setAttributes({text: title}, true);
65314         } else {
65315             base = {
65316                 type: 'text',
65317                 x: 0,
65318                 y: 0,
65319                 text: title
65320             };
65321             displaySprite = me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle));
65322             surface.renderItem(displaySprite);
65323         }
65324         bbox = displaySprite.getBBox();
65325         pad = me.dashSize + me.label.padding;
65326
65327         if (rotate) {
65328             y -= ((me.length / 2) - (bbox.height / 2));
65329             if (position == 'left') {
65330                 x -= (maxWidth + pad + (bbox.width / 2));
65331             }
65332             else {
65333                 x += (maxWidth + pad + bbox.width - (bbox.width / 2));
65334             }
65335             me.bbox.width += bbox.width + 10;
65336         }
65337         else {
65338             x += (me.length / 2) - (bbox.width * 0.5);
65339             if (position == 'top') {
65340                 y -= (maxHeight + pad + (bbox.height * 0.3));
65341             }
65342             else {
65343                 y += (maxHeight + pad + (bbox.height * 0.8));
65344             }
65345             me.bbox.height += bbox.height + 10;
65346         }
65347         displaySprite.setAttributes({
65348             translate: {
65349                 x: x,
65350                 y: y
65351             }
65352         }, true);
65353     }
65354 });
65355
65356 /**
65357  * @class Ext.chart.axis.Category
65358  * @extends Ext.chart.axis.Axis
65359  *
65360  * A type of axis that displays items in categories. This axis is generally used to
65361  * display categorical information like names of items, month names, quarters, etc.
65362  * but no quantitative values. For that other type of information `Number`
65363  * axis are more suitable.
65364  *
65365  * As with other axis you can set the position of the axis and its title. For example:
65366  *
65367  *     @example
65368  *     var store = Ext.create('Ext.data.JsonStore', {
65369  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
65370  *         data: [
65371  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
65372  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
65373  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
65374  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
65375  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
65376  *         ]
65377  *     });
65378  *
65379  *     Ext.create('Ext.chart.Chart', {
65380  *         renderTo: Ext.getBody(),
65381  *         width: 500,
65382  *         height: 300,
65383  *         store: store,
65384  *         axes: [{
65385  *             type: 'Numeric',
65386  *             grid: true,
65387  *             position: 'left',
65388  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
65389  *             title: 'Sample Values',
65390  *             grid: {
65391  *                 odd: {
65392  *                     opacity: 1,
65393  *                     fill: '#ddd',
65394  *                     stroke: '#bbb',
65395  *                     'stroke-width': 1
65396  *                 }
65397  *             },
65398  *             minimum: 0,
65399  *             adjustMinimumByMajorUnit: 0
65400  *         }, {
65401  *             type: 'Category',
65402  *             position: 'bottom',
65403  *             fields: ['name'],
65404  *             title: 'Sample Metrics',
65405  *             grid: true,
65406  *             label: {
65407  *                 rotate: {
65408  *                     degrees: 315
65409  *                 }
65410  *             }
65411  *         }],
65412  *         series: [{
65413  *             type: 'area',
65414  *             highlight: false,
65415  *             axis: 'left',
65416  *             xField: 'name',
65417  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
65418  *             style: {
65419  *                 opacity: 0.93
65420  *             }
65421  *         }]
65422  *     });
65423  *
65424  * In this example with set the category axis to the bottom of the surface, bound the axis to
65425  * the `name` property and set as title _Month of the Year_.
65426  */
65427 Ext.define('Ext.chart.axis.Category', {
65428
65429     /* Begin Definitions */
65430
65431     extend: 'Ext.chart.axis.Axis',
65432
65433     alternateClassName: 'Ext.chart.CategoryAxis',
65434
65435     alias: 'axis.category',
65436
65437     /* End Definitions */
65438
65439     /**
65440      * A list of category names to display along this axis.
65441      * @property {String} categoryNames
65442      */
65443     categoryNames: null,
65444
65445     /**
65446      * Indicates whether or not to calculate the number of categories (ticks and
65447      * labels) when there is not enough room to display all labels on the axis.
65448      * If set to true, the axis will determine the number of categories to plot.
65449      * If not, all categories will be plotted.
65450      *
65451      * @property calculateCategoryCount
65452      * @type Boolean
65453      */
65454     calculateCategoryCount: false,
65455
65456     // @private creates an array of labels to be used when rendering.
65457     setLabels: function() {
65458         var store = this.chart.store,
65459             fields = this.fields,
65460             ln = fields.length,
65461             i;
65462
65463         this.labels = [];
65464         store.each(function(record) {
65465             for (i = 0; i < ln; i++) {
65466                 this.labels.push(record.get(fields[i]));
65467             }
65468         }, this);
65469     },
65470
65471     // @private calculates labels positions and marker positions for rendering.
65472     applyData: function() {
65473         this.callParent();
65474         this.setLabels();
65475         var count = this.chart.store.getCount();
65476         return {
65477             from: 0,
65478             to: count,
65479             power: 1,
65480             step: 1,
65481             steps: count - 1
65482         };
65483     }
65484 });
65485
65486 /**
65487  * @class Ext.chart.axis.Gauge
65488  * @extends Ext.chart.axis.Abstract
65489  *
65490  * Gauge Axis is the axis to be used with a Gauge series. The Gauge axis
65491  * displays numeric data from an interval defined by the `minimum`, `maximum` and
65492  * `step` configuration properties. The placement of the numeric data can be changed
65493  * by altering the `margin` option that is set to `10` by default.
65494  *
65495  * A possible configuration for this axis would look like:
65496  *
65497  *     axes: [{
65498  *         type: 'gauge',
65499  *         position: 'gauge',
65500  *         minimum: 0,
65501  *         maximum: 100,
65502  *         steps: 10,
65503  *         margin: 7
65504  *     }],
65505  */
65506 Ext.define('Ext.chart.axis.Gauge', {
65507
65508     /* Begin Definitions */
65509
65510     extend: 'Ext.chart.axis.Abstract',
65511
65512     /* End Definitions */
65513
65514     /**
65515      * @cfg {Number} minimum (required)
65516      * The minimum value of the interval to be displayed in the axis.
65517      */
65518
65519     /**
65520      * @cfg {Number} maximum (required)
65521      * The maximum value of the interval to be displayed in the axis.
65522      */
65523
65524     /**
65525      * @cfg {Number} steps (required)
65526      * The number of steps and tick marks to add to the interval.
65527      */
65528
65529     /**
65530      * @cfg {Number} [margin=10]
65531      * The offset positioning of the tick marks and labels in pixels.
65532      */
65533
65534     /**
65535      * @cfg {String} title
65536      * The title for the Axis.
65537      */
65538
65539     position: 'gauge',
65540
65541     alias: 'axis.gauge',
65542
65543     drawAxis: function(init) {
65544         var chart = this.chart,
65545             surface = chart.surface,
65546             bbox = chart.chartBBox,
65547             centerX = bbox.x + (bbox.width / 2),
65548             centerY = bbox.y + bbox.height,
65549             margin = this.margin || 10,
65550             rho = Math.min(bbox.width, 2 * bbox.height) /2 + margin,
65551             sprites = [], sprite,
65552             steps = this.steps,
65553             i, pi = Math.PI,
65554             cos = Math.cos,
65555             sin = Math.sin;
65556
65557         if (this.sprites && !chart.resizing) {
65558             this.drawLabel();
65559             return;
65560         }
65561
65562         if (this.margin >= 0) {
65563             if (!this.sprites) {
65564                 //draw circles
65565                 for (i = 0; i <= steps; i++) {
65566                     sprite = surface.add({
65567                         type: 'path',
65568                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
65569                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
65570                                     'L', centerX + rho * cos(i / steps * pi - pi),
65571                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
65572                         stroke: '#ccc'
65573                     });
65574                     sprite.setAttributes({
65575                         hidden: false
65576                     }, true);
65577                     sprites.push(sprite);
65578                 }
65579             } else {
65580                 sprites = this.sprites;
65581                 //draw circles
65582                 for (i = 0; i <= steps; i++) {
65583                     sprites[i].setAttributes({
65584                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
65585                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
65586                                'L', centerX + rho * cos(i / steps * pi - pi),
65587                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
65588                         stroke: '#ccc'
65589                     }, true);
65590                 }
65591             }
65592         }
65593         this.sprites = sprites;
65594         this.drawLabel();
65595         if (this.title) {
65596             this.drawTitle();
65597         }
65598     },
65599
65600     drawTitle: function() {
65601         var me = this,
65602             chart = me.chart,
65603             surface = chart.surface,
65604             bbox = chart.chartBBox,
65605             labelSprite = me.titleSprite,
65606             labelBBox;
65607
65608         if (!labelSprite) {
65609             me.titleSprite = labelSprite = surface.add({
65610                 type: 'text',
65611                 zIndex: 2
65612             });
65613         }
65614         labelSprite.setAttributes(Ext.apply({
65615             text: me.title
65616         }, me.label || {}), true);
65617         labelBBox = labelSprite.getBBox();
65618         labelSprite.setAttributes({
65619             x: bbox.x + (bbox.width / 2) - (labelBBox.width / 2),
65620             y: bbox.y + bbox.height - (labelBBox.height / 2) - 4
65621         }, true);
65622     },
65623
65624     /**
65625      * Updates the {@link #title} of this axis.
65626      * @param {String} title
65627      */
65628     setTitle: function(title) {
65629         this.title = title;
65630         this.drawTitle();
65631     },
65632
65633     drawLabel: function() {
65634         var chart = this.chart,
65635             surface = chart.surface,
65636             bbox = chart.chartBBox,
65637             centerX = bbox.x + (bbox.width / 2),
65638             centerY = bbox.y + bbox.height,
65639             margin = this.margin || 10,
65640             rho = Math.min(bbox.width, 2 * bbox.height) /2 + 2 * margin,
65641             round = Math.round,
65642             labelArray = [], label,
65643             maxValue = this.maximum || 0,
65644             steps = this.steps, i = 0,
65645             adjY,
65646             pi = Math.PI,
65647             cos = Math.cos,
65648             sin = Math.sin,
65649             labelConf = this.label,
65650             renderer = labelConf.renderer || function(v) { return v; };
65651
65652         if (!this.labelArray) {
65653             //draw scale
65654             for (i = 0; i <= steps; i++) {
65655                 // TODO Adjust for height of text / 2 instead
65656                 adjY = (i === 0 || i === steps) ? 7 : 0;
65657                 label = surface.add({
65658                     type: 'text',
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                     'text-anchor': 'middle',
65663                     'stroke-width': 0.2,
65664                     zIndex: 10,
65665                     stroke: '#333'
65666                 });
65667                 label.setAttributes({
65668                     hidden: false
65669                 }, true);
65670                 labelArray.push(label);
65671             }
65672         }
65673         else {
65674             labelArray = this.labelArray;
65675             //draw values
65676             for (i = 0; i <= steps; i++) {
65677                 // TODO Adjust for height of text / 2 instead
65678                 adjY = (i === 0 || i === steps) ? 7 : 0;
65679                 labelArray[i].setAttributes({
65680                     text: renderer(round(i / steps * maxValue)),
65681                     x: centerX + rho * cos(i / steps * pi - pi),
65682                     y: centerY + rho * sin(i / steps * pi - pi) - adjY
65683                 }, true);
65684             }
65685         }
65686         this.labelArray = labelArray;
65687     }
65688 });
65689 /**
65690  * @class Ext.chart.axis.Numeric
65691  * @extends Ext.chart.axis.Axis
65692  *
65693  * An axis to handle numeric values. This axis is used for quantitative data as
65694  * opposed to the category axis. You can set mininum and maximum values to the
65695  * axis so that the values are bound to that. If no values are set, then the
65696  * scale will auto-adjust to the values.
65697  *
65698  *     @example
65699  *     var store = Ext.create('Ext.data.JsonStore', {
65700  *          fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
65701  *          data: [
65702  *              {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
65703  *              {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
65704  *              {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
65705  *              {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
65706  *              {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
65707  *          ]
65708  *     });
65709  *
65710  *     Ext.create('Ext.chart.Chart', {
65711  *         renderTo: Ext.getBody(),
65712  *         width: 500,
65713  *         height: 300,
65714  *         store: store,
65715  *         axes: [{
65716  *             type: 'Numeric',
65717  *             grid: true,
65718  *             position: 'left',
65719  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
65720  *             title: 'Sample Values',
65721  *             grid: {
65722  *                 odd: {
65723  *                     opacity: 1,
65724  *                     fill: '#ddd',
65725  *                     stroke: '#bbb',
65726  *                     'stroke-width': 1
65727  *                 }
65728  *             },
65729  *             minimum: 0,
65730  *             adjustMinimumByMajorUnit: 0
65731  *         }, {
65732  *             type: 'Category',
65733  *             position: 'bottom',
65734  *             fields: ['name'],
65735  *             title: 'Sample Metrics',
65736  *             grid: true,
65737  *             label: {
65738  *                 rotate: {
65739  *                     degrees: 315
65740  *                 }
65741  *             }
65742  *         }],
65743  *         series: [{
65744  *             type: 'area',
65745  *             highlight: false,
65746  *             axis: 'left',
65747  *             xField: 'name',
65748  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
65749  *             style: {
65750  *                 opacity: 0.93
65751  *             }
65752  *         }]
65753  *     });
65754  *
65755  * In this example we create an axis of Numeric type. We set a minimum value so that
65756  * even if all series have values greater than zero, the grid starts at zero. We bind
65757  * the axis onto the left part of the surface by setting `position` to `left`.
65758  * We bind three different store fields to this axis by setting `fields` to an array.
65759  * We set the title of the axis to _Number of Hits_ by using the `title` property.
65760  * We use a `grid` configuration to set odd background rows to a certain style and even rows
65761  * to be transparent/ignored.
65762  */
65763 Ext.define('Ext.chart.axis.Numeric', {
65764
65765     /* Begin Definitions */
65766
65767     extend: 'Ext.chart.axis.Axis',
65768
65769     alternateClassName: 'Ext.chart.NumericAxis',
65770
65771     /* End Definitions */
65772
65773     type: 'numeric',
65774
65775     alias: 'axis.numeric',
65776
65777     constructor: function(config) {
65778         var me = this,
65779             hasLabel = !!(config.label && config.label.renderer),
65780             label;
65781
65782         me.callParent([config]);
65783         label = me.label;
65784         if (me.roundToDecimal === false) {
65785             return;
65786         }
65787         if (!hasLabel) {
65788             label.renderer = function(v) {
65789                 return me.roundToDecimal(v, me.decimals);
65790             };
65791         }
65792     },
65793
65794     roundToDecimal: function(v, dec) {
65795         var val = Math.pow(10, dec || 0);
65796         return Math.floor(v * val) / val;
65797     },
65798
65799     /**
65800      * The minimum value drawn by the axis. If not set explicitly, the axis
65801      * minimum will be calculated automatically.
65802      *
65803      * @property {Number} minimum
65804      */
65805     minimum: NaN,
65806
65807     /**
65808      * The maximum value drawn by the axis. If not set explicitly, the axis
65809      * maximum will be calculated automatically.
65810      *
65811      * @property {Number} maximum
65812      */
65813     maximum: NaN,
65814
65815     /**
65816      * The number of decimals to round the value to.
65817      *
65818      * @property {Number} decimals
65819      */
65820     decimals: 2,
65821
65822     /**
65823      * The scaling algorithm to use on this axis. May be "linear" or
65824      * "logarithmic".  Currently only linear scale is implemented.
65825      *
65826      * @property {String} scale
65827      * @private
65828      */
65829     scale: "linear",
65830
65831     /**
65832      * Indicates the position of the axis relative to the chart
65833      *
65834      * @property {String} position
65835      */
65836     position: 'left',
65837
65838     /**
65839      * Indicates whether to extend maximum beyond data's maximum to the nearest
65840      * majorUnit.
65841      *
65842      * @property {Boolean} adjustMaximumByMajorUnit
65843      */
65844     adjustMaximumByMajorUnit: false,
65845
65846     /**
65847      * Indicates whether to extend the minimum beyond data's minimum to the
65848      * nearest majorUnit.
65849      *
65850      * @property {Boolean} adjustMinimumByMajorUnit
65851      */
65852     adjustMinimumByMajorUnit: false,
65853
65854     // @private apply data.
65855     applyData: function() {
65856         this.callParent();
65857         return this.calcEnds();
65858     }
65859 });
65860
65861 /**
65862  * @class Ext.chart.axis.Radial
65863  * @extends Ext.chart.axis.Abstract
65864  * @ignore
65865  */
65866 Ext.define('Ext.chart.axis.Radial', {
65867
65868     /* Begin Definitions */
65869
65870     extend: 'Ext.chart.axis.Abstract',
65871
65872     /* End Definitions */
65873
65874     position: 'radial',
65875
65876     alias: 'axis.radial',
65877
65878     drawAxis: function(init) {
65879         var chart = this.chart,
65880             surface = chart.surface,
65881             bbox = chart.chartBBox,
65882             store = chart.store,
65883             l = store.getCount(),
65884             centerX = bbox.x + (bbox.width / 2),
65885             centerY = bbox.y + (bbox.height / 2),
65886             rho = Math.min(bbox.width, bbox.height) /2,
65887             sprites = [], sprite,
65888             steps = this.steps,
65889             i, j, pi2 = Math.PI * 2,
65890             cos = Math.cos, sin = Math.sin;
65891
65892         if (this.sprites && !chart.resizing) {
65893             this.drawLabel();
65894             return;
65895         }
65896
65897         if (!this.sprites) {
65898             //draw circles
65899             for (i = 1; i <= steps; i++) {
65900                 sprite = surface.add({
65901                     type: 'circle',
65902                     x: centerX,
65903                     y: centerY,
65904                     radius: Math.max(rho * i / steps, 0),
65905                     stroke: '#ccc'
65906                 });
65907                 sprite.setAttributes({
65908                     hidden: false
65909                 }, true);
65910                 sprites.push(sprite);
65911             }
65912             //draw lines
65913             store.each(function(rec, i) {
65914                 sprite = surface.add({
65915                     type: 'path',
65916                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(i / l * pi2), centerY + rho * sin(i / l * pi2), 'Z'],
65917                     stroke: '#ccc'
65918                 });
65919                 sprite.setAttributes({
65920                     hidden: false
65921                 }, true);
65922                 sprites.push(sprite);
65923             });
65924         } else {
65925             sprites = this.sprites;
65926             //draw circles
65927             for (i = 0; i < steps; i++) {
65928                 sprites[i].setAttributes({
65929                     x: centerX,
65930                     y: centerY,
65931                     radius: Math.max(rho * (i + 1) / steps, 0),
65932                     stroke: '#ccc'
65933                 }, true);
65934             }
65935             //draw lines
65936             store.each(function(rec, j) {
65937                 sprites[i + j].setAttributes({
65938                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(j / l * pi2), centerY + rho * sin(j / l * pi2), 'Z'],
65939                     stroke: '#ccc'
65940                 }, true);
65941             });
65942         }
65943         this.sprites = sprites;
65944
65945         this.drawLabel();
65946     },
65947
65948     drawLabel: function() {
65949         var chart = this.chart,
65950             surface = chart.surface,
65951             bbox = chart.chartBBox,
65952             store = chart.store,
65953             centerX = bbox.x + (bbox.width / 2),
65954             centerY = bbox.y + (bbox.height / 2),
65955             rho = Math.min(bbox.width, bbox.height) /2,
65956             max = Math.max, round = Math.round,
65957             labelArray = [], label,
65958             fields = [], nfields,
65959             categories = [], xField,
65960             aggregate = !this.maximum,
65961             maxValue = this.maximum || 0,
65962             steps = this.steps, i = 0, j, dx, dy,
65963             pi2 = Math.PI * 2,
65964             cos = Math.cos, sin = Math.sin,
65965             display = this.label.display,
65966             draw = display !== 'none',
65967             margin = 10;
65968
65969         if (!draw) {
65970             return;
65971         }
65972
65973         //get all rendered fields
65974         chart.series.each(function(series) {
65975             fields.push(series.yField);
65976             xField = series.xField;
65977         });
65978         
65979         //get maxValue to interpolate
65980         store.each(function(record, i) {
65981             if (aggregate) {
65982                 for (i = 0, nfields = fields.length; i < nfields; i++) {
65983                     maxValue = max(+record.get(fields[i]), maxValue);
65984                 }
65985             }
65986             categories.push(record.get(xField));
65987         });
65988         if (!this.labelArray) {
65989             if (display != 'categories') {
65990                 //draw scale
65991                 for (i = 1; i <= steps; i++) {
65992                     label = surface.add({
65993                         type: 'text',
65994                         text: round(i / steps * maxValue),
65995                         x: centerX,
65996                         y: centerY - rho * i / steps,
65997                         'text-anchor': 'middle',
65998                         'stroke-width': 0.1,
65999                         stroke: '#333'
66000                     });
66001                     label.setAttributes({
66002                         hidden: false
66003                     }, true);
66004                     labelArray.push(label);
66005                 }
66006             }
66007             if (display != 'scale') {
66008                 //draw text
66009                 for (j = 0, steps = categories.length; j < steps; j++) {
66010                     dx = cos(j / steps * pi2) * (rho + margin);
66011                     dy = sin(j / steps * pi2) * (rho + margin);
66012                     label = surface.add({
66013                         type: 'text',
66014                         text: categories[j],
66015                         x: centerX + dx,
66016                         y: centerY + dy,
66017                         'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
66018                     });
66019                     label.setAttributes({
66020                         hidden: false
66021                     }, true);
66022                     labelArray.push(label);
66023                 }
66024             }
66025         }
66026         else {
66027             labelArray = this.labelArray;
66028             if (display != 'categories') {
66029                 //draw values
66030                 for (i = 0; i < steps; i++) {
66031                     labelArray[i].setAttributes({
66032                         text: round((i + 1) / steps * maxValue),
66033                         x: centerX,
66034                         y: centerY - rho * (i + 1) / steps,
66035                         'text-anchor': 'middle',
66036                         'stroke-width': 0.1,
66037                         stroke: '#333'
66038                     }, true);
66039                 }
66040             }
66041             if (display != 'scale') {
66042                 //draw text
66043                 for (j = 0, steps = categories.length; j < steps; j++) {
66044                     dx = cos(j / steps * pi2) * (rho + margin);
66045                     dy = sin(j / steps * pi2) * (rho + margin);
66046                     if (labelArray[i + j]) {
66047                         labelArray[i + j].setAttributes({
66048                             type: 'text',
66049                             text: categories[j],
66050                             x: centerX + dx,
66051                             y: centerY + dy,
66052                             'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
66053                         }, true);
66054                     }
66055                 }
66056             }
66057         }
66058         this.labelArray = labelArray;
66059     }
66060 });
66061 /**
66062  * @author Ed Spencer
66063  *
66064  * AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
66065  * but offers a set of methods used by both of those subclasses.
66066  * 
66067  * We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
66068  * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what 
66069  * AbstractStore is and is not.
66070  * 
66071  * AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be 
66072  * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a 
66073  * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.
66074  * 
66075  * AbstractStore provides a few helpful methods such as {@link #load} and {@link #sync}, which load and save data
66076  * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
66077  * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data - 
66078  * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
66079  * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.
66080  * 
66081  * The store provides filtering and sorting support. This sorting/filtering can happen on the client side
66082  * or can be completed on the server. This is controlled by the {@link Ext.data.Store#remoteSort remoteSort} and
66083  * {@link Ext.data.Store#remoteFilter remoteFilter} config options. For more information see the {@link #sort} and
66084  * {@link Ext.data.Store#filter filter} methods.
66085  */
66086 Ext.define('Ext.data.AbstractStore', {
66087     requires: ['Ext.util.MixedCollection', 'Ext.data.Operation', 'Ext.util.Filter'],
66088     
66089     mixins: {
66090         observable: 'Ext.util.Observable',
66091         sortable: 'Ext.util.Sortable'
66092     },
66093     
66094     statics: {
66095         create: function(store){
66096             if (!store.isStore) {
66097                 if (!store.type) {
66098                     store.type = 'store';
66099                 }
66100                 store = Ext.createByAlias('store.' + store.type, store);
66101             }
66102             return store;
66103         }    
66104     },
66105     
66106     remoteSort  : false,
66107     remoteFilter: false,
66108
66109     /**
66110      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy
66111      * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
66112      * see {@link #setProxy} for details.
66113      */
66114
66115     /**
66116      * @cfg {Boolean/Object} autoLoad
66117      * If data is not specified, and if autoLoad is true or an Object, this store's load method is automatically called
66118      * after creation. If the value of autoLoad is an Object, this Object will be passed to the store's load method.
66119      * Defaults to false.
66120      */
66121     autoLoad: false,
66122
66123     /**
66124      * @cfg {Boolean} autoSync
66125      * True to automatically sync the Store with its Proxy after every edit to one of its Records. Defaults to false.
66126      */
66127     autoSync: false,
66128
66129     /**
66130      * @property {String} batchUpdateMode
66131      * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
66132      * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
66133      * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
66134      * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
66135      */
66136     batchUpdateMode: 'operation',
66137
66138     /**
66139      * @property {Boolean} filterOnLoad
66140      * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
66141      * Defaults to true, ignored if {@link Ext.data.Store#remoteFilter remoteFilter} is true
66142      */
66143     filterOnLoad: true,
66144
66145     /**
66146      * @property {Boolean} sortOnLoad
66147      * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
66148      * Defaults to true, igored if {@link Ext.data.Store#remoteSort remoteSort} is true
66149      */
66150     sortOnLoad: true,
66151
66152     /**
66153      * @property {Boolean} implicitModel
66154      * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's
66155      * constructor instead of a model constructor or name.
66156      * @private
66157      */
66158     implicitModel: false,
66159
66160     /**
66161      * @property {String} defaultProxyType
66162      * The string type of the Proxy to create if none is specified. This defaults to creating a
66163      * {@link Ext.data.proxy.Memory memory proxy}.
66164      */
66165     defaultProxyType: 'memory',
66166
66167     /**
66168      * @property {Boolean} isDestroyed
66169      * True if the Store has already been destroyed. If this is true, the reference to Store should be deleted
66170      * as it will not function correctly any more.
66171      */
66172     isDestroyed: false,
66173
66174     isStore: true,
66175
66176     /**
66177      * @cfg {String} storeId
66178      * Unique identifier for this store. If present, this Store will be registered with the {@link Ext.data.StoreManager},
66179      * making it easy to reuse elsewhere. Defaults to undefined.
66180      */
66181     
66182     /**
66183      * @cfg {Object[]} fields
66184      * This may be used in place of specifying a {@link #model} configuration. The fields should be a 
66185      * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
66186      * with these fields. In general this configuration option should be avoided, it exists for the purposes of
66187      * backwards compatibility. For anything more complicated, such as specifying a particular id property or
66188      * assocations, a {@link Ext.data.Model} should be defined and specified for the {@link #model}
66189      * config.
66190      */
66191
66192     /**
66193      * @cfg {String} model
66194      * Name of the {@link Ext.data.Model Model} associated with this store.
66195      * The string is used as an argument for {@link Ext.ModelManager#getModel}.
66196      */
66197
66198     sortRoot: 'data',
66199     
66200     //documented above
66201     constructor: function(config) {
66202         var me = this,
66203             filters;
66204         
66205         me.addEvents(
66206             /**
66207              * @event add
66208              * Fired when a Model instance has been added to this Store
66209              * @param {Ext.data.Store} store The store
66210              * @param {Ext.data.Model[]} records The Model instances that were added
66211              * @param {Number} index The index at which the instances were inserted
66212              */
66213             'add',
66214
66215             /**
66216              * @event remove
66217              * Fired when a Model instance has been removed from this Store
66218              * @param {Ext.data.Store} store The Store object
66219              * @param {Ext.data.Model} record The record that was removed
66220              * @param {Number} index The index of the record that was removed
66221              */
66222             'remove',
66223             
66224             /**
66225              * @event update
66226              * Fires when a Model instance has been updated
66227              * @param {Ext.data.Store} this
66228              * @param {Ext.data.Model} record The Model instance that was updated
66229              * @param {String} operation The update operation being performed. Value may be one of:
66230              *
66231              *     Ext.data.Model.EDIT
66232              *     Ext.data.Model.REJECT
66233              *     Ext.data.Model.COMMIT
66234              */
66235             'update',
66236
66237             /**
66238              * @event datachanged
66239              * Fires whenever the records in the Store have changed in some way - this could include adding or removing
66240              * records, or updating the data in existing records
66241              * @param {Ext.data.Store} this The data store
66242              */
66243             'datachanged',
66244
66245             /**
66246              * @event beforeload
66247              * Fires before a request is made for a new data object. If the beforeload handler returns false the load
66248              * action will be canceled.
66249              * @param {Ext.data.Store} store This Store
66250              * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
66251              * load the Store
66252              */
66253             'beforeload',
66254
66255             /**
66256              * @event load
66257              * Fires whenever the store reads data from a remote data source.
66258              * @param {Ext.data.Store} this
66259              * @param {Ext.data.Model[]} records An array of records
66260              * @param {Boolean} successful True if the operation was successful.
66261              */
66262             'load',
66263             
66264             /**
66265              * @event write
66266              * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
66267              * @param {Ext.data.Store} store This Store
66268              * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object that was used in
66269              * the write
66270              */
66271             'write',
66272
66273             /**
66274              * @event beforesync
66275              * Fired before a call to {@link #sync} is executed. Return false from any listener to cancel the synv
66276              * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
66277              */
66278             'beforesync',
66279             /**
66280              * @event clear
66281              * Fired after the {@link #removeAll} method is called.
66282              * @param {Ext.data.Store} this
66283              */
66284             'clear'
66285         );
66286         
66287         Ext.apply(me, config);
66288         // don't use *config* anymore from here on... use *me* instead...
66289
66290         /**
66291          * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
66292          * at which point this is cleared.
66293          * @private
66294          * @property {Ext.data.Model[]} removed
66295          */
66296         me.removed = [];
66297
66298         me.mixins.observable.constructor.apply(me, arguments);
66299         me.model = Ext.ModelManager.getModel(me.model);
66300
66301         /**
66302          * @property {Object} modelDefaults
66303          * @private
66304          * A set of default values to be applied to every model instance added via {@link #insert} or created via {@link #create}.
66305          * This is used internally by associations to set foreign keys and other fields. See the Association classes source code
66306          * for examples. This should not need to be used by application developers.
66307          */
66308         Ext.applyIf(me, {
66309             modelDefaults: {}
66310         });
66311
66312         //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
66313         if (!me.model && me.fields) {
66314             me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
66315                 extend: 'Ext.data.Model',
66316                 fields: me.fields,
66317                 proxy: me.proxy || me.defaultProxyType
66318             });
66319
66320             delete me.fields;
66321
66322             me.implicitModel = true;
66323         }
66324         
66325         if (!me.model) {
66326             if (Ext.isDefined(Ext.global.console)) {
66327                 Ext.global.console.warn('Store defined with no model. You may have mistyped the model name.');
66328             }
66329         }
66330
66331         //ensures that the Proxy is instantiated correctly
66332         me.setProxy(me.proxy || me.model.getProxy());
66333
66334         if (me.id && !me.storeId) {
66335             me.storeId = me.id;
66336             delete me.id;
66337         }
66338
66339         if (me.storeId) {
66340             Ext.data.StoreManager.register(me);
66341         }
66342         
66343         me.mixins.sortable.initSortable.call(me);        
66344         
66345         /**
66346          * @property {Ext.util.MixedCollection} filters
66347          * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
66348          */
66349         filters = me.decodeFilters(me.filters);
66350         me.filters = Ext.create('Ext.util.MixedCollection');
66351         me.filters.addAll(filters);
66352     },
66353
66354     /**
66355      * Sets the Store's Proxy by string, config object or Proxy instance
66356      * @param {String/Object/Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
66357      * or an Ext.data.proxy.Proxy instance
66358      * @return {Ext.data.proxy.Proxy} The attached Proxy object
66359      */
66360     setProxy: function(proxy) {
66361         var me = this;
66362         
66363         if (proxy instanceof Ext.data.proxy.Proxy) {
66364             proxy.setModel(me.model);
66365         } else {
66366             if (Ext.isString(proxy)) {
66367                 proxy = {
66368                     type: proxy    
66369                 };
66370             }
66371             Ext.applyIf(proxy, {
66372                 model: me.model
66373             });
66374             
66375             proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
66376         }
66377         
66378         me.proxy = proxy;
66379         
66380         return me.proxy;
66381     },
66382
66383     /**
66384      * Returns the proxy currently attached to this proxy instance
66385      * @return {Ext.data.proxy.Proxy} The Proxy instance
66386      */
66387     getProxy: function() {
66388         return this.proxy;
66389     },
66390
66391     //saves any phantom records
66392     create: function(data, options) {
66393         var me = this,
66394             instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
66395             operation;
66396         
66397         options = options || {};
66398
66399         Ext.applyIf(options, {
66400             action : 'create',
66401             records: [instance]
66402         });
66403
66404         operation = Ext.create('Ext.data.Operation', options);
66405
66406         me.proxy.create(operation, me.onProxyWrite, me);
66407         
66408         return instance;
66409     },
66410
66411     read: function() {
66412         return this.load.apply(this, arguments);
66413     },
66414
66415     onProxyRead: Ext.emptyFn,
66416
66417     update: function(options) {
66418         var me = this,
66419             operation;
66420         options = options || {};
66421
66422         Ext.applyIf(options, {
66423             action : 'update',
66424             records: me.getUpdatedRecords()
66425         });
66426
66427         operation = Ext.create('Ext.data.Operation', options);
66428
66429         return me.proxy.update(operation, me.onProxyWrite, me);
66430     },
66431
66432     /**
66433      * @private
66434      * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
66435      * the updates provided by the Proxy
66436      */
66437     onProxyWrite: function(operation) {
66438         var me = this,
66439             success = operation.wasSuccessful(),
66440             records = operation.getRecords();
66441
66442         switch (operation.action) {
66443             case 'create':
66444                 me.onCreateRecords(records, operation, success);
66445                 break;
66446             case 'update':
66447                 me.onUpdateRecords(records, operation, success);
66448                 break;
66449             case 'destroy':
66450                 me.onDestroyRecords(records, operation, success);
66451                 break;
66452         }
66453
66454         if (success) {
66455             me.fireEvent('write', me, operation);
66456             me.fireEvent('datachanged', me);
66457         }
66458         //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
66459         Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
66460     },
66461
66462
66463     //tells the attached proxy to destroy the given records
66464     destroy: function(options) {
66465         var me = this,
66466             operation;
66467             
66468         options = options || {};
66469
66470         Ext.applyIf(options, {
66471             action : 'destroy',
66472             records: me.getRemovedRecords()
66473         });
66474
66475         operation = Ext.create('Ext.data.Operation', options);
66476
66477         return me.proxy.destroy(operation, me.onProxyWrite, me);
66478     },
66479
66480     /**
66481      * @private
66482      * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
66483      * to onProxyWrite.
66484      */
66485     onBatchOperationComplete: function(batch, operation) {
66486         return this.onProxyWrite(operation);
66487     },
66488
66489     /**
66490      * @private
66491      * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
66492      * and updates the Store's internal data MixedCollection.
66493      */
66494     onBatchComplete: function(batch, operation) {
66495         var me = this,
66496             operations = batch.operations,
66497             length = operations.length,
66498             i;
66499
66500         me.suspendEvents();
66501
66502         for (i = 0; i < length; i++) {
66503             me.onProxyWrite(operations[i]);
66504         }
66505
66506         me.resumeEvents();
66507
66508         me.fireEvent('datachanged', me);
66509     },
66510
66511     onBatchException: function(batch, operation) {
66512         // //decide what to do... could continue with the next operation
66513         // batch.start();
66514         //
66515         // //or retry the last operation
66516         // batch.retry();
66517     },
66518
66519     /**
66520      * @private
66521      * Filter function for new records.
66522      */
66523     filterNew: function(item) {
66524         // only want phantom records that are valid
66525         return item.phantom === true && item.isValid();
66526     },
66527
66528     /**
66529      * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
66530      * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
66531      * @return {Ext.data.Model[]} The Model instances
66532      */
66533     getNewRecords: function() {
66534         return [];
66535     },
66536
66537     /**
66538      * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
66539      * @return {Ext.data.Model[]} The updated Model instances
66540      */
66541     getUpdatedRecords: function() {
66542         return [];
66543     },
66544
66545     /**
66546      * @private
66547      * Filter function for updated records.
66548      */
66549     filterUpdated: function(item) {
66550         // only want dirty records, not phantoms that are valid
66551         return item.dirty === true && item.phantom !== true && item.isValid();
66552     },
66553
66554     /**
66555      * Returns any records that have been removed from the store but not yet destroyed on the proxy.
66556      * @return {Ext.data.Model[]} The removed Model instances
66557      */
66558     getRemovedRecords: function() {
66559         return this.removed;
66560     },
66561
66562     filter: function(filters, value) {
66563
66564     },
66565
66566     /**
66567      * @private
66568      * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
66569      * @param {Object[]} filters The filters array
66570      * @return {Ext.util.Filter[]} Array of Ext.util.Filter objects
66571      */
66572     decodeFilters: function(filters) {
66573         if (!Ext.isArray(filters)) {
66574             if (filters === undefined) {
66575                 filters = [];
66576             } else {
66577                 filters = [filters];
66578             }
66579         }
66580
66581         var length = filters.length,
66582             Filter = Ext.util.Filter,
66583             config, i;
66584
66585         for (i = 0; i < length; i++) {
66586             config = filters[i];
66587
66588             if (!(config instanceof Filter)) {
66589                 Ext.apply(config, {
66590                     root: 'data'
66591                 });
66592
66593                 //support for 3.x style filters where a function can be defined as 'fn'
66594                 if (config.fn) {
66595                     config.filterFn = config.fn;
66596                 }
66597
66598                 //support a function to be passed as a filter definition
66599                 if (typeof config == 'function') {
66600                     config = {
66601                         filterFn: config
66602                     };
66603                 }
66604
66605                 filters[i] = new Filter(config);
66606             }
66607         }
66608
66609         return filters;
66610     },
66611
66612     clearFilter: function(supressEvent) {
66613
66614     },
66615
66616     isFiltered: function() {
66617
66618     },
66619
66620     filterBy: function(fn, scope) {
66621
66622     },
66623     
66624     /**
66625      * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
66626      * and deleted records in the store, updating the Store's internal representation of the records
66627      * as each operation completes.
66628      */
66629     sync: function() {
66630         var me        = this,
66631             options   = {},
66632             toCreate  = me.getNewRecords(),
66633             toUpdate  = me.getUpdatedRecords(),
66634             toDestroy = me.getRemovedRecords(),
66635             needsSync = false;
66636
66637         if (toCreate.length > 0) {
66638             options.create = toCreate;
66639             needsSync = true;
66640         }
66641
66642         if (toUpdate.length > 0) {
66643             options.update = toUpdate;
66644             needsSync = true;
66645         }
66646
66647         if (toDestroy.length > 0) {
66648             options.destroy = toDestroy;
66649             needsSync = true;
66650         }
66651
66652         if (needsSync && me.fireEvent('beforesync', options) !== false) {
66653             me.proxy.batch(options, me.getBatchListeners());
66654         }
66655     },
66656
66657
66658     /**
66659      * @private
66660      * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
66661      * This is broken out into a separate function to allow for customisation of the listeners
66662      * @return {Object} The listeners object
66663      */
66664     getBatchListeners: function() {
66665         var me = this,
66666             listeners = {
66667                 scope: me,
66668                 exception: me.onBatchException
66669             };
66670
66671         if (me.batchUpdateMode == 'operation') {
66672             listeners.operationcomplete = me.onBatchOperationComplete;
66673         } else {
66674             listeners.complete = me.onBatchComplete;
66675         }
66676
66677         return listeners;
66678     },
66679
66680     //deprecated, will be removed in 5.0
66681     save: function() {
66682         return this.sync.apply(this, arguments);
66683     },
66684
66685     /**
66686      * Loads the Store using its configured {@link #proxy}.
66687      * @param {Object} options (optional) config object. This is passed into the {@link Ext.data.Operation Operation}
66688      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
66689      */
66690     load: function(options) {
66691         var me = this,
66692             operation;
66693
66694         options = options || {};
66695
66696         Ext.applyIf(options, {
66697             action : 'read',
66698             filters: me.filters.items,
66699             sorters: me.getSorters()
66700         });
66701         
66702         operation = Ext.create('Ext.data.Operation', options);
66703
66704         if (me.fireEvent('beforeload', me, operation) !== false) {
66705             me.loading = true;
66706             me.proxy.read(operation, me.onProxyLoad, me);
66707         }
66708         
66709         return me;
66710     },
66711
66712     /**
66713      * @private
66714      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
66715      * @param {Ext.data.Model} record The model instance that was edited
66716      */
66717     afterEdit : function(record) {
66718         var me = this;
66719         
66720         if (me.autoSync) {
66721             me.sync();
66722         }
66723         
66724         me.fireEvent('update', me, record, Ext.data.Model.EDIT);
66725     },
66726
66727     /**
66728      * @private
66729      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
66730      * @param {Ext.data.Model} record The model instance that was edited
66731      */
66732     afterReject : function(record) {
66733         this.fireEvent('update', this, record, Ext.data.Model.REJECT);
66734     },
66735
66736     /**
66737      * @private
66738      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
66739      * @param {Ext.data.Model} record The model instance that was edited
66740      */
66741     afterCommit : function(record) {
66742         this.fireEvent('update', this, record, Ext.data.Model.COMMIT);
66743     },
66744
66745     clearData: Ext.emptyFn,
66746
66747     destroyStore: function() {
66748         var me = this;
66749         
66750         if (!me.isDestroyed) {
66751             if (me.storeId) {
66752                 Ext.data.StoreManager.unregister(me);
66753             }
66754             me.clearData();
66755             me.data = null;
66756             me.tree = null;
66757             // Ext.destroy(this.proxy);
66758             me.reader = me.writer = null;
66759             me.clearListeners();
66760             me.isDestroyed = true;
66761
66762             if (me.implicitModel) {
66763                 Ext.destroy(me.model);
66764             }
66765         }
66766     },
66767     
66768     doSort: function(sorterFn) {
66769         var me = this;
66770         if (me.remoteSort) {
66771             //the load function will pick up the new sorters and request the sorted data from the proxy
66772             me.load();
66773         } else {
66774             me.data.sortBy(sorterFn);
66775             me.fireEvent('datachanged', me);
66776         }
66777     },
66778
66779     getCount: Ext.emptyFn,
66780
66781     getById: Ext.emptyFn,
66782     
66783     /**
66784      * Removes all records from the store. This method does a "fast remove",
66785      * individual remove events are not called. The {@link #clear} event is
66786      * fired upon completion.
66787      * @method
66788      */
66789     removeAll: Ext.emptyFn,
66790     // individual substores should implement a "fast" remove
66791     // and fire a clear event afterwards
66792
66793     /**
66794      * Returns true if the Store is currently performing a load operation
66795      * @return {Boolean} True if the Store is currently loading
66796      */
66797     isLoading: function() {
66798         return !!this.loading;
66799      }
66800 });
66801
66802 /**
66803  * @class Ext.util.Grouper
66804  * @extends Ext.util.Sorter
66805
66806 Represents a single grouper that can be applied to a Store. The grouper works
66807 in the same fashion as the {@link Ext.util.Sorter}.
66808
66809  * @markdown
66810  */
66811  
66812 Ext.define('Ext.util.Grouper', {
66813
66814     /* Begin Definitions */
66815
66816     extend: 'Ext.util.Sorter',
66817
66818     /* End Definitions */
66819
66820     /**
66821      * Returns the value for grouping to be used.
66822      * @param {Ext.data.Model} instance The Model instance
66823      * @return {String} The group string for this model
66824      */
66825     getGroupString: function(instance) {
66826         return instance.get(this.property);
66827     }
66828 });
66829 /**
66830  * @author Ed Spencer
66831  * @class Ext.data.Store
66832  * @extends Ext.data.AbstractStore
66833  *
66834  * <p>The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
66835  * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
66836  * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.</p>
66837  *
66838  * <p>Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:</p>
66839  *
66840 <pre><code>
66841 // Set up a {@link Ext.data.Model model} to use in our Store
66842 Ext.define('User', {
66843     extend: 'Ext.data.Model',
66844     fields: [
66845         {name: 'firstName', type: 'string'},
66846         {name: 'lastName',  type: 'string'},
66847         {name: 'age',       type: 'int'},
66848         {name: 'eyeColor',  type: 'string'}
66849     ]
66850 });
66851
66852 var myStore = Ext.create('Ext.data.Store', {
66853     model: 'User',
66854     proxy: {
66855         type: 'ajax',
66856         url : '/users.json',
66857         reader: {
66858             type: 'json',
66859             root: 'users'
66860         }
66861     },
66862     autoLoad: true
66863 });
66864 </code></pre>
66865
66866  * <p>In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
66867  * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
66868  * {@link Ext.data.reader.Json see the docs on JsonReader} for details.</p>
66869  *
66870  * <p><u>Inline data</u></p>
66871  *
66872  * <p>Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #data}
66873  * into Model instances:</p>
66874  *
66875 <pre><code>
66876 Ext.create('Ext.data.Store', {
66877     model: 'User',
66878     data : [
66879         {firstName: 'Ed',    lastName: 'Spencer'},
66880         {firstName: 'Tommy', lastName: 'Maintz'},
66881         {firstName: 'Aaron', lastName: 'Conran'},
66882         {firstName: 'Jamie', lastName: 'Avins'}
66883     ]
66884 });
66885 </code></pre>
66886  *
66887  * <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
66888  * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
66889  * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).</p>
66890  *
66891  * <p>Additional data can also be loaded locally using {@link #add}.</p>
66892  *
66893  * <p><u>Loading Nested Data</u></p>
66894  *
66895  * <p>Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
66896  * 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
66897  * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
66898  * docs for a full explanation:</p>
66899  *
66900 <pre><code>
66901 var store = Ext.create('Ext.data.Store', {
66902     autoLoad: true,
66903     model: "User",
66904     proxy: {
66905         type: 'ajax',
66906         url : 'users.json',
66907         reader: {
66908             type: 'json',
66909             root: 'users'
66910         }
66911     }
66912 });
66913 </code></pre>
66914  *
66915  * <p>Which would consume a response like this:</p>
66916  *
66917 <pre><code>
66918 {
66919     "users": [
66920         {
66921             "id": 1,
66922             "name": "Ed",
66923             "orders": [
66924                 {
66925                     "id": 10,
66926                     "total": 10.76,
66927                     "status": "invoiced"
66928                 },
66929                 {
66930                     "id": 11,
66931                     "total": 13.45,
66932                     "status": "shipped"
66933                 }
66934             ]
66935         }
66936     ]
66937 }
66938 </code></pre>
66939  *
66940  * <p>See the {@link Ext.data.reader.Reader} intro docs for a full explanation.</p>
66941  *
66942  * <p><u>Filtering and Sorting</u></p>
66943  *
66944  * <p>Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
66945  * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
66946  * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
66947  *
66948 <pre><code>
66949 var store = Ext.create('Ext.data.Store', {
66950     model: 'User',
66951     sorters: [
66952         {
66953             property : 'age',
66954             direction: 'DESC'
66955         },
66956         {
66957             property : 'firstName',
66958             direction: 'ASC'
66959         }
66960     ],
66961
66962     filters: [
66963         {
66964             property: 'firstName',
66965             value   : /Ed/
66966         }
66967     ]
66968 });
66969 </code></pre>
66970  *
66971  * <p>The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
66972  * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
66973  * perform these operations instead.</p>
66974  *
66975  * <p>Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
66976  * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
66977  * default {@link #sortOnFilter} is set to true, which means that your sorters are automatically reapplied if using local sorting.</p>
66978  *
66979 <pre><code>
66980 store.filter('eyeColor', 'Brown');
66981 </code></pre>
66982  *
66983  * <p>Change the sorting at any time by calling {@link #sort}:</p>
66984  *
66985 <pre><code>
66986 store.sort('height', 'ASC');
66987 </code></pre>
66988  *
66989  * <p>Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
66990  * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
66991  * to the MixedCollection:</p>
66992  *
66993 <pre><code>
66994 store.sorters.add(new Ext.util.Sorter({
66995     property : 'shoeSize',
66996     direction: 'ASC'
66997 }));
66998
66999 store.sort();
67000 </code></pre>
67001  *
67002  * <p><u>Registering with StoreManager</u></p>
67003  *
67004  * <p>Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link Ext.data.StoreManager StoreManager}.
67005  * This makes it easy to reuse the same store in multiple views:</p>
67006  *
67007  <pre><code>
67008 //this store can be used several times
67009 Ext.create('Ext.data.Store', {
67010     model: 'User',
67011     storeId: 'usersStore'
67012 });
67013
67014 new Ext.List({
67015     store: 'usersStore',
67016
67017     //other config goes here
67018 });
67019
67020 new Ext.view.View({
67021     store: 'usersStore',
67022
67023     //other config goes here
67024 });
67025 </code></pre>
67026  *
67027  * <p><u>Further Reading</u></p>
67028  *
67029  * <p>Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
67030  * pieces and how they fit together, see:</p>
67031  *
67032  * <ul style="list-style-type: disc; padding-left: 25px">
67033  * <li>{@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used</li>
67034  * <li>{@link Ext.data.Model Model} - the core class in the data package</li>
67035  * <li>{@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response</li>
67036  * </ul>
67037  *
67038  */
67039 Ext.define('Ext.data.Store', {
67040     extend: 'Ext.data.AbstractStore',
67041
67042     alias: 'store.store',
67043
67044     requires: ['Ext.data.StoreManager', 'Ext.ModelManager', 'Ext.data.Model', 'Ext.util.Grouper'],
67045     uses: ['Ext.data.proxy.Memory'],
67046
67047     /**
67048      * @cfg {Boolean} remoteSort
67049      * True to defer any sorting operation to the server. If false, sorting is done locally on the client. Defaults to <tt>false</tt>.
67050      */
67051     remoteSort: false,
67052
67053     /**
67054      * @cfg {Boolean} remoteFilter
67055      * True to defer any filtering operation to the server. If false, filtering is done locally on the client. Defaults to <tt>false</tt>.
67056      */
67057     remoteFilter: false,
67058
67059     /**
67060      * @cfg {Boolean} remoteGroup
67061      * True if the grouping should apply on the server side, false if it is local only.  If the
67062      * grouping is local, it can be applied immediately to the data.  If it is remote, then it will simply act as a
67063      * helper, automatically sending the grouping information to the server.
67064      */
67065     remoteGroup : false,
67066
67067     /**
67068      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
67069      * object or a Proxy instance - see {@link #setProxy} for details.
67070      */
67071
67072     /**
67073      * @cfg {Object[]/Ext.data.Model[]} data Optional array of Model instances or data objects to load locally. See "Inline data" above for details.
67074      */
67075
67076     /**
67077      * @property {String} groupField
67078      * The field by which to group data in the store. Internally, grouping is very similar to sorting - the
67079      * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
67080      * level of grouping, and groups can be fetched via the {@link #getGroups} method.
67081      */
67082     groupField: undefined,
67083
67084     /**
67085      * The direction in which sorting should be applied when grouping. Defaults to "ASC" - the other supported value is "DESC"
67086      * @property groupDir
67087      * @type String
67088      */
67089     groupDir: "ASC",
67090
67091     /**
67092      * @cfg {Number} pageSize
67093      * The number of records considered to form a 'page'. This is used to power the built-in
67094      * paging using the nextPage and previousPage functions. Defaults to 25.
67095      */
67096     pageSize: 25,
67097
67098     /**
67099      * The page that the Store has most recently loaded (see {@link #loadPage})
67100      * @property currentPage
67101      * @type Number
67102      */
67103     currentPage: 1,
67104
67105     /**
67106      * @cfg {Boolean} clearOnPageLoad True to empty the store when loading another page via {@link #loadPage},
67107      * {@link #nextPage} or {@link #previousPage}. Setting to false keeps existing records, allowing
67108      * large data sets to be loaded one page at a time but rendered all together.
67109      */
67110     clearOnPageLoad: true,
67111
67112     /**
67113      * @property {Boolean} loading
67114      * True if the Store is currently loading via its Proxy
67115      * @private
67116      */
67117     loading: false,
67118
67119     /**
67120      * @cfg {Boolean} sortOnFilter For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
67121      * causing the sorters to be reapplied after filtering. Defaults to true
67122      */
67123     sortOnFilter: true,
67124
67125     /**
67126      * @cfg {Boolean} buffered
67127      * Allow the store to buffer and pre-fetch pages of records. This is to be used in conjunction with a view will
67128      * tell the store to pre-fetch records ahead of a time.
67129      */
67130     buffered: false,
67131
67132     /**
67133      * @cfg {Number} purgePageCount
67134      * The number of pages to keep in the cache before purging additional records. A value of 0 indicates to never purge the prefetched data.
67135      * This option is only relevant when the {@link #buffered} option is set to true.
67136      */
67137     purgePageCount: 5,
67138
67139     isStore: true,
67140
67141     onClassExtended: function(cls, data) {
67142         var model = data.model;
67143
67144         if (typeof model == 'string') {
67145             var onBeforeClassCreated = data.onBeforeClassCreated;
67146
67147             data.onBeforeClassCreated = function(cls, data) {
67148                 var me = this;
67149
67150                 Ext.require(model, function() {
67151                     onBeforeClassCreated.call(me, cls, data);
67152                 });
67153             };
67154         }
67155     },
67156
67157     /**
67158      * Creates the store.
67159      * @param {Object} config (optional) Config object
67160      */
67161     constructor: function(config) {
67162         // Clone the config so we don't modify the original config object
67163         config = Ext.Object.merge({}, config);
67164
67165         var me = this,
67166             groupers = config.groupers || me.groupers,
67167             groupField = config.groupField || me.groupField,
67168             proxy,
67169             data;
67170
67171         if (config.buffered || me.buffered) {
67172             me.prefetchData = Ext.create('Ext.util.MixedCollection', false, function(record) {
67173                 return record.index;
67174             });
67175             me.pendingRequests = [];
67176             me.pagesRequested = [];
67177
67178             me.sortOnLoad = false;
67179             me.filterOnLoad = false;
67180         }
67181
67182         me.addEvents(
67183             /**
67184              * @event beforeprefetch
67185              * Fires before a prefetch occurs. Return false to cancel.
67186              * @param {Ext.data.Store} this
67187              * @param {Ext.data.Operation} operation The associated operation
67188              */
67189             'beforeprefetch',
67190             /**
67191              * @event groupchange
67192              * Fired whenever the grouping in the grid changes
67193              * @param {Ext.data.Store} store The store
67194              * @param {Ext.util.Grouper[]} groupers The array of grouper objects
67195              */
67196             'groupchange',
67197             /**
67198              * @event load
67199              * Fires whenever records have been prefetched
67200              * @param {Ext.data.Store} this
67201              * @param {Ext.util.Grouper[]} records An array of records
67202              * @param {Boolean} successful True if the operation was successful.
67203              * @param {Ext.data.Operation} operation The associated operation
67204              */
67205             'prefetch'
67206         );
67207         data = config.data || me.data;
67208
67209         /**
67210          * The MixedCollection that holds this store's local cache of records
67211          * @property data
67212          * @type Ext.util.MixedCollection
67213          */
67214         me.data = Ext.create('Ext.util.MixedCollection', false, function(record) {
67215             return record.internalId;
67216         });
67217
67218         if (data) {
67219             me.inlineData = data;
67220             delete config.data;
67221         }
67222
67223         if (!groupers && groupField) {
67224             groupers = [{
67225                 property : groupField,
67226                 direction: config.groupDir || me.groupDir
67227             }];
67228         }
67229         delete config.groupers;
67230
67231         /**
67232          * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store
67233          * @property groupers
67234          * @type Ext.util.MixedCollection
67235          */
67236         me.groupers = Ext.create('Ext.util.MixedCollection');
67237         me.groupers.addAll(me.decodeGroupers(groupers));
67238
67239         this.callParent([config]);
67240         // don't use *config* anymore from here on... use *me* instead...
67241
67242         if (me.groupers.items.length) {
67243             me.sort(me.groupers.items, 'prepend', false);
67244         }
67245
67246         proxy = me.proxy;
67247         data = me.inlineData;
67248
67249         if (data) {
67250             if (proxy instanceof Ext.data.proxy.Memory) {
67251                 proxy.data = data;
67252                 me.read();
67253             } else {
67254                 me.add.apply(me, data);
67255             }
67256
67257             me.sort();
67258             delete me.inlineData;
67259         } else if (me.autoLoad) {
67260             Ext.defer(me.load, 10, me, [typeof me.autoLoad === 'object' ? me.autoLoad: undefined]);
67261             // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
67262             // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
67263         }
67264     },
67265
67266     onBeforeSort: function() {
67267         var groupers = this.groupers;
67268         if (groupers.getCount() > 0) {
67269             this.sort(groupers.items, 'prepend', false);
67270         }
67271     },
67272
67273     /**
67274      * @private
67275      * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
67276      * @param {Object[]} groupers The groupers array
67277      * @return {Ext.util.Grouper[]} Array of Ext.util.Grouper objects
67278      */
67279     decodeGroupers: function(groupers) {
67280         if (!Ext.isArray(groupers)) {
67281             if (groupers === undefined) {
67282                 groupers = [];
67283             } else {
67284                 groupers = [groupers];
67285             }
67286         }
67287
67288         var length  = groupers.length,
67289             Grouper = Ext.util.Grouper,
67290             config, i;
67291
67292         for (i = 0; i < length; i++) {
67293             config = groupers[i];
67294
67295             if (!(config instanceof Grouper)) {
67296                 if (Ext.isString(config)) {
67297                     config = {
67298                         property: config
67299                     };
67300                 }
67301
67302                 Ext.applyIf(config, {
67303                     root     : 'data',
67304                     direction: "ASC"
67305                 });
67306
67307                 //support for 3.x style sorters where a function can be defined as 'fn'
67308                 if (config.fn) {
67309                     config.sorterFn = config.fn;
67310                 }
67311
67312                 //support a function to be passed as a sorter definition
67313                 if (typeof config == 'function') {
67314                     config = {
67315                         sorterFn: config
67316                     };
67317                 }
67318
67319                 groupers[i] = new Grouper(config);
67320             }
67321         }
67322
67323         return groupers;
67324     },
67325
67326     /**
67327      * Group data in the store
67328      * @param {String/Object[]} groupers Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
67329      * or an Array of grouper configurations.
67330      * @param {String} direction The overall direction to group the data by. Defaults to "ASC".
67331      */
67332     group: function(groupers, direction) {
67333         var me = this,
67334             hasNew = false,
67335             grouper,
67336             newGroupers;
67337
67338         if (Ext.isArray(groupers)) {
67339             newGroupers = groupers;
67340         } else if (Ext.isObject(groupers)) {
67341             newGroupers = [groupers];
67342         } else if (Ext.isString(groupers)) {
67343             grouper = me.groupers.get(groupers);
67344
67345             if (!grouper) {
67346                 grouper = {
67347                     property : groupers,
67348                     direction: direction
67349                 };
67350                 newGroupers = [grouper];
67351             } else if (direction === undefined) {
67352                 grouper.toggle();
67353             } else {
67354                 grouper.setDirection(direction);
67355             }
67356         }
67357
67358         if (newGroupers && newGroupers.length) {
67359             hasNew = true;
67360             newGroupers = me.decodeGroupers(newGroupers);
67361             me.groupers.clear();
67362             me.groupers.addAll(newGroupers);
67363         }
67364
67365         if (me.remoteGroup) {
67366             me.load({
67367                 scope: me,
67368                 callback: me.fireGroupChange
67369             });
67370         } else {
67371             // need to explicitly force a sort if we have groupers
67372             me.sort(null, null, null, hasNew);
67373             me.fireGroupChange();
67374         }
67375     },
67376
67377     /**
67378      * Clear any groupers in the store
67379      */
67380     clearGrouping: function(){
67381         var me = this;
67382         // Clear any groupers we pushed on to the sorters
67383         me.groupers.each(function(grouper){
67384             me.sorters.remove(grouper);
67385         });
67386         me.groupers.clear();
67387         if (me.remoteGroup) {
67388             me.load({
67389                 scope: me,
67390                 callback: me.fireGroupChange
67391             });
67392         } else {
67393             me.sort();
67394             me.fireEvent('groupchange', me, me.groupers);
67395         }
67396     },
67397
67398     /**
67399      * Checks if the store is currently grouped
67400      * @return {Boolean} True if the store is grouped.
67401      */
67402     isGrouped: function() {
67403         return this.groupers.getCount() > 0;
67404     },
67405
67406     /**
67407      * Fires the groupchange event. Abstracted out so we can use it
67408      * as a callback
67409      * @private
67410      */
67411     fireGroupChange: function(){
67412         this.fireEvent('groupchange', this, this.groupers);
67413     },
67414
67415     /**
67416      * Returns an array containing the result of applying grouping to the records in this store. See {@link #groupField},
67417      * {@link #groupDir} and {@link #getGroupString}. Example for a store containing records with a color field:
67418 <pre><code>
67419 var myStore = Ext.create('Ext.data.Store', {
67420     groupField: 'color',
67421     groupDir  : 'DESC'
67422 });
67423
67424 myStore.getGroups(); //returns:
67425 [
67426     {
67427         name: 'yellow',
67428         children: [
67429             //all records where the color field is 'yellow'
67430         ]
67431     },
67432     {
67433         name: 'red',
67434         children: [
67435             //all records where the color field is 'red'
67436         ]
67437     }
67438 ]
67439 </code></pre>
67440      * @param {String} groupName (Optional) Pass in an optional groupName argument to access a specific group as defined by {@link #getGroupString}
67441      * @return {Object/Object[]} The grouped data
67442      */
67443     getGroups: function(requestGroupString) {
67444         var records = this.data.items,
67445             length = records.length,
67446             groups = [],
67447             pointers = {},
67448             record,
67449             groupStr,
67450             group,
67451             i;
67452
67453         for (i = 0; i < length; i++) {
67454             record = records[i];
67455             groupStr = this.getGroupString(record);
67456             group = pointers[groupStr];
67457
67458             if (group === undefined) {
67459                 group = {
67460                     name: groupStr,
67461                     children: []
67462                 };
67463
67464                 groups.push(group);
67465                 pointers[groupStr] = group;
67466             }
67467
67468             group.children.push(record);
67469         }
67470
67471         return requestGroupString ? pointers[requestGroupString] : groups;
67472     },
67473
67474     /**
67475      * @private
67476      * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
67477      * matching a certain group.
67478      */
67479     getGroupsForGrouper: function(records, grouper) {
67480         var length = records.length,
67481             groups = [],
67482             oldValue,
67483             newValue,
67484             record,
67485             group,
67486             i;
67487
67488         for (i = 0; i < length; i++) {
67489             record = records[i];
67490             newValue = grouper.getGroupString(record);
67491
67492             if (newValue !== oldValue) {
67493                 group = {
67494                     name: newValue,
67495                     grouper: grouper,
67496                     records: []
67497                 };
67498                 groups.push(group);
67499             }
67500
67501             group.records.push(record);
67502
67503             oldValue = newValue;
67504         }
67505
67506         return groups;
67507     },
67508
67509     /**
67510      * @private
67511      * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
67512      * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
67513      * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
67514      * @param {Ext.data.Model[]} records The set or subset of records to group
67515      * @param {Number} grouperIndex The grouper index to retrieve
67516      * @return {Object[]} The grouped records
67517      */
67518     getGroupsForGrouperIndex: function(records, grouperIndex) {
67519         var me = this,
67520             groupers = me.groupers,
67521             grouper = groupers.getAt(grouperIndex),
67522             groups = me.getGroupsForGrouper(records, grouper),
67523             length = groups.length,
67524             i;
67525
67526         if (grouperIndex + 1 < groupers.length) {
67527             for (i = 0; i < length; i++) {
67528                 groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
67529             }
67530         }
67531
67532         for (i = 0; i < length; i++) {
67533             groups[i].depth = grouperIndex;
67534         }
67535
67536         return groups;
67537     },
67538
67539     /**
67540      * @private
67541      * <p>Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
67542      * this case grouping by genre and then author in a fictional books dataset):</p>
67543 <pre><code>
67544 [
67545     {
67546         name: 'Fantasy',
67547         depth: 0,
67548         records: [
67549             //book1, book2, book3, book4
67550         ],
67551         children: [
67552             {
67553                 name: 'Rowling',
67554                 depth: 1,
67555                 records: [
67556                     //book1, book2
67557                 ]
67558             },
67559             {
67560                 name: 'Tolkein',
67561                 depth: 1,
67562                 records: [
67563                     //book3, book4
67564                 ]
67565             }
67566         ]
67567     }
67568 ]
67569 </code></pre>
67570      * @param {Boolean} sort True to call {@link #sort} before finding groups. Sorting is required to make grouping
67571      * function correctly so this should only be set to false if the Store is known to already be sorted correctly
67572      * (defaults to true)
67573      * @return {Object[]} The group data
67574      */
67575     getGroupData: function(sort) {
67576         var me = this;
67577         if (sort !== false) {
67578             me.sort();
67579         }
67580
67581         return me.getGroupsForGrouperIndex(me.data.items, 0);
67582     },
67583
67584     /**
67585      * <p>Returns the string to group on for a given model instance. The default implementation of this method returns
67586      * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
67587      * group by the first letter of a model's 'name' field, use the following code:</p>
67588 <pre><code>
67589 Ext.create('Ext.data.Store', {
67590     groupDir: 'ASC',
67591     getGroupString: function(instance) {
67592         return instance.get('name')[0];
67593     }
67594 });
67595 </code></pre>
67596      * @param {Ext.data.Model} instance The model instance
67597      * @return {String} The string to compare when forming groups
67598      */
67599     getGroupString: function(instance) {
67600         var group = this.groupers.first();
67601         if (group) {
67602             return instance.get(group.property);
67603         }
67604         return '';
67605     },
67606     /**
67607      * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
67608      * See also <code>{@link #add}</code>.
67609      * @param {Number} index The start index at which to insert the passed Records.
67610      * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
67611      */
67612     insert: function(index, records) {
67613         var me = this,
67614             sync = false,
67615             i,
67616             record,
67617             len;
67618
67619         records = [].concat(records);
67620         for (i = 0, len = records.length; i < len; i++) {
67621             record = me.createModel(records[i]);
67622             record.set(me.modelDefaults);
67623             // reassign the model in the array in case it wasn't created yet
67624             records[i] = record;
67625
67626             me.data.insert(index + i, record);
67627             record.join(me);
67628
67629             sync = sync || record.phantom === true;
67630         }
67631
67632         if (me.snapshot) {
67633             me.snapshot.addAll(records);
67634         }
67635
67636         me.fireEvent('add', me, records, index);
67637         me.fireEvent('datachanged', me);
67638         if (me.autoSync && sync) {
67639             me.sync();
67640         }
67641     },
67642
67643     /**
67644      * Adds Model instance to the Store. This method accepts either:
67645      *
67646      * - An array of Model instances or Model configuration objects.
67647      * - Any number of Model instance or Model configuration object arguments.
67648      *
67649      * The new Model instances will be added at the end of the existing collection.
67650      *
67651      * Sample usage:
67652      *
67653      *     myStore.add({some: 'data'}, {some: 'other data'});
67654      *
67655      * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
67656      * or Model configuration objects, or variable number of Model instance or config arguments.
67657      * @return {Ext.data.Model[]} The model instances that were added
67658      */
67659     add: function(records) {
67660         //accept both a single-argument array of records, or any number of record arguments
67661         if (!Ext.isArray(records)) {
67662             records = Array.prototype.slice.apply(arguments);
67663         }
67664
67665         var me = this,
67666             i = 0,
67667             length = records.length,
67668             record;
67669
67670         for (; i < length; i++) {
67671             record = me.createModel(records[i]);
67672             // reassign the model in the array in case it wasn't created yet
67673             records[i] = record;
67674         }
67675
67676         me.insert(me.data.length, records);
67677
67678         return records;
67679     },
67680
67681     /**
67682      * Converts a literal to a model, if it's not a model already
67683      * @private
67684      * @param record {Ext.data.Model/Object} The record to create
67685      * @return {Ext.data.Model}
67686      */
67687     createModel: function(record) {
67688         if (!record.isModel) {
67689             record = Ext.ModelManager.create(record, this.model);
67690         }
67691
67692         return record;
67693     },
67694
67695     /**
67696      * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
67697      * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
67698      * Returning <tt>false</tt> aborts and exits the iteration.
67699      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
67700      * Defaults to the current {@link Ext.data.Model Record} in the iteration.
67701      */
67702     each: function(fn, scope) {
67703         this.data.each(fn, scope);
67704     },
67705
67706     /**
67707      * Removes the given record from the Store, firing the 'remove' event for each instance that is removed, plus a single
67708      * 'datachanged' event after removal.
67709      * @param {Ext.data.Model/Ext.data.Model[]} records The Ext.data.Model instance or array of instances to remove
67710      */
67711     remove: function(records, /* private */ isMove) {
67712         if (!Ext.isArray(records)) {
67713             records = [records];
67714         }
67715
67716         /*
67717          * Pass the isMove parameter if we know we're going to be re-inserting this record
67718          */
67719         isMove = isMove === true;
67720         var me = this,
67721             sync = false,
67722             i = 0,
67723             length = records.length,
67724             isPhantom,
67725             index,
67726             record;
67727
67728         for (; i < length; i++) {
67729             record = records[i];
67730             index = me.data.indexOf(record);
67731
67732             if (me.snapshot) {
67733                 me.snapshot.remove(record);
67734             }
67735
67736             if (index > -1) {
67737                 isPhantom = record.phantom === true;
67738                 if (!isMove && !isPhantom) {
67739                     // don't push phantom records onto removed
67740                     me.removed.push(record);
67741                 }
67742
67743                 record.unjoin(me);
67744                 me.data.remove(record);
67745                 sync = sync || !isPhantom;
67746
67747                 me.fireEvent('remove', me, record, index);
67748             }
67749         }
67750
67751         me.fireEvent('datachanged', me);
67752         if (!isMove && me.autoSync && sync) {
67753             me.sync();
67754         }
67755     },
67756
67757     /**
67758      * Removes the model instance at the given index
67759      * @param {Number} index The record index
67760      */
67761     removeAt: function(index) {
67762         var record = this.getAt(index);
67763
67764         if (record) {
67765             this.remove(record);
67766         }
67767     },
67768
67769     /**
67770      * <p>Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
67771      * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
67772      * instances into the Store and calling an optional callback if required. Example usage:</p>
67773      *
67774 <pre><code>
67775 store.load({
67776     scope   : this,
67777     callback: function(records, operation, success) {
67778         //the {@link Ext.data.Operation operation} object contains all of the details of the load operation
67779         console.log(records);
67780     }
67781 });
67782 </code></pre>
67783      *
67784      * <p>If the callback scope does not need to be set, a function can simply be passed:</p>
67785      *
67786 <pre><code>
67787 store.load(function(records, operation, success) {
67788     console.log('loaded records');
67789 });
67790 </code></pre>
67791      *
67792      * @param {Object/Function} options (Optional) config object, passed into the Ext.data.Operation object before loading.
67793      */
67794     load: function(options) {
67795         var me = this;
67796
67797         options = options || {};
67798
67799         if (Ext.isFunction(options)) {
67800             options = {
67801                 callback: options
67802             };
67803         }
67804
67805         Ext.applyIf(options, {
67806             groupers: me.groupers.items,
67807             page: me.currentPage,
67808             start: (me.currentPage - 1) * me.pageSize,
67809             limit: me.pageSize,
67810             addRecords: false
67811         });
67812
67813         return me.callParent([options]);
67814     },
67815
67816     /**
67817      * @private
67818      * Called internally when a Proxy has completed a load request
67819      */
67820     onProxyLoad: function(operation) {
67821         var me = this,
67822             resultSet = operation.getResultSet(),
67823             records = operation.getRecords(),
67824             successful = operation.wasSuccessful();
67825
67826         if (resultSet) {
67827             me.totalCount = resultSet.total;
67828         }
67829
67830         if (successful) {
67831             me.loadRecords(records, operation);
67832         }
67833
67834         me.loading = false;
67835         me.fireEvent('load', me, records, successful);
67836
67837         //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
67838         //People are definitely using this so can't deprecate safely until 2.x
67839         me.fireEvent('read', me, records, operation.wasSuccessful());
67840
67841         //this is a callback that would have been passed to the 'read' function and is optional
67842         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
67843     },
67844
67845     /**
67846      * Create any new records when a write is returned from the server.
67847      * @private
67848      * @param {Ext.data.Model[]} records The array of new records
67849      * @param {Ext.data.Operation} operation The operation that just completed
67850      * @param {Boolean} success True if the operation was successful
67851      */
67852     onCreateRecords: function(records, operation, success) {
67853         if (success) {
67854             var i = 0,
67855                 data = this.data,
67856                 snapshot = this.snapshot,
67857                 length = records.length,
67858                 originalRecords = operation.records,
67859                 record,
67860                 original,
67861                 index;
67862
67863             /*
67864              * Loop over each record returned from the server. Assume they are
67865              * returned in order of how they were sent. If we find a matching
67866              * record, replace it with the newly created one.
67867              */
67868             for (; i < length; ++i) {
67869                 record = records[i];
67870                 original = originalRecords[i];
67871                 if (original) {
67872                     index = data.indexOf(original);
67873                     if (index > -1) {
67874                         data.removeAt(index);
67875                         data.insert(index, record);
67876                     }
67877                     if (snapshot) {
67878                         index = snapshot.indexOf(original);
67879                         if (index > -1) {
67880                             snapshot.removeAt(index);
67881                             snapshot.insert(index, record);
67882                         }
67883                     }
67884                     record.phantom = false;
67885                     record.join(this);
67886                 }
67887             }
67888         }
67889     },
67890
67891     /**
67892      * Update any records when a write is returned from the server.
67893      * @private
67894      * @param {Ext.data.Model[]} records The array of updated records
67895      * @param {Ext.data.Operation} operation The operation that just completed
67896      * @param {Boolean} success True if the operation was successful
67897      */
67898     onUpdateRecords: function(records, operation, success){
67899         if (success) {
67900             var i = 0,
67901                 length = records.length,
67902                 data = this.data,
67903                 snapshot = this.snapshot,
67904                 record;
67905
67906             for (; i < length; ++i) {
67907                 record = records[i];
67908                 data.replace(record);
67909                 if (snapshot) {
67910                     snapshot.replace(record);
67911                 }
67912                 record.join(this);
67913             }
67914         }
67915     },
67916
67917     /**
67918      * Remove any records when a write is returned from the server.
67919      * @private
67920      * @param {Ext.data.Model[]} records The array of removed records
67921      * @param {Ext.data.Operation} operation The operation that just completed
67922      * @param {Boolean} success True if the operation was successful
67923      */
67924     onDestroyRecords: function(records, operation, success){
67925         if (success) {
67926             var me = this,
67927                 i = 0,
67928                 length = records.length,
67929                 data = me.data,
67930                 snapshot = me.snapshot,
67931                 record;
67932
67933             for (; i < length; ++i) {
67934                 record = records[i];
67935                 record.unjoin(me);
67936                 data.remove(record);
67937                 if (snapshot) {
67938                     snapshot.remove(record);
67939                 }
67940             }
67941             me.removed = [];
67942         }
67943     },
67944
67945     //inherit docs
67946     getNewRecords: function() {
67947         return this.data.filterBy(this.filterNew).items;
67948     },
67949
67950     //inherit docs
67951     getUpdatedRecords: function() {
67952         return this.data.filterBy(this.filterUpdated).items;
67953     },
67954
67955     /**
67956      * Filters the loaded set of records by a given set of filters.
67957      *
67958      * Filtering by single field:
67959      *
67960      *     store.filter("email", /\.com$/);
67961      *
67962      * Using multiple filters:
67963      *
67964      *     store.filter([
67965      *         {property: "email", value: /\.com$/},
67966      *         {filterFn: function(item) { return item.get("age") > 10; }}
67967      *     ]);
67968      *
67969      * Using Ext.util.Filter instances instead of config objects
67970      * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
67971      *
67972      *     store.filter([
67973      *         Ext.create('Ext.util.Filter', {property: "email", value: /\.com$/, root: 'data'}),
67974      *         Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get("age") > 10; }, root: 'data'})
67975      *     ]);
67976      *
67977      * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data. These are stored internally on the store,
67978      * but the filtering itself is done on the Store's {@link Ext.util.MixedCollection MixedCollection}. See
67979      * MixedCollection's {@link Ext.util.MixedCollection#filter filter} method for filter syntax. Alternatively,
67980      * pass in a property string
67981      * @param {String} value (optional) value to filter by (only if using a property string as the first argument)
67982      */
67983     filter: function(filters, value) {
67984         if (Ext.isString(filters)) {
67985             filters = {
67986                 property: filters,
67987                 value: value
67988             };
67989         }
67990
67991         var me = this,
67992             decoded = me.decodeFilters(filters),
67993             i = 0,
67994             doLocalSort = me.sortOnFilter && !me.remoteSort,
67995             length = decoded.length;
67996
67997         for (; i < length; i++) {
67998             me.filters.replace(decoded[i]);
67999         }
68000
68001         if (me.remoteFilter) {
68002             //the load function will pick up the new filters and request the filtered data from the proxy
68003             me.load();
68004         } else {
68005             /**
68006              * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
68007              * records when a filter is removed or changed
68008              * @property snapshot
68009              * @type Ext.util.MixedCollection
68010              */
68011             if (me.filters.getCount()) {
68012                 me.snapshot = me.snapshot || me.data.clone();
68013                 me.data = me.data.filter(me.filters.items);
68014
68015                 if (doLocalSort) {
68016                     me.sort();
68017                 }
68018                 // fire datachanged event if it hasn't already been fired by doSort
68019                 if (!doLocalSort || me.sorters.length < 1) {
68020                     me.fireEvent('datachanged', me);
68021                 }
68022             }
68023         }
68024     },
68025
68026     /**
68027      * Revert to a view of the Record cache with no filtering applied.
68028      * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
68029      * {@link #datachanged} event.
68030      */
68031     clearFilter: function(suppressEvent) {
68032         var me = this;
68033
68034         me.filters.clear();
68035
68036         if (me.remoteFilter) {
68037             me.load();
68038         } else if (me.isFiltered()) {
68039             me.data = me.snapshot.clone();
68040             delete me.snapshot;
68041
68042             if (suppressEvent !== true) {
68043                 me.fireEvent('datachanged', me);
68044             }
68045         }
68046     },
68047
68048     /**
68049      * Returns true if this store is currently filtered
68050      * @return {Boolean}
68051      */
68052     isFiltered: function() {
68053         var snapshot = this.snapshot;
68054         return !! snapshot && snapshot !== this.data;
68055     },
68056
68057     /**
68058      * Filter by a function. The specified function will be called for each
68059      * Record in this Store. If the function returns <tt>true</tt> the Record is included,
68060      * otherwise it is filtered out.
68061      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
68062      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
68063      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
68064      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
68065      * </ul>
68066      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
68067      */
68068     filterBy: function(fn, scope) {
68069         var me = this;
68070
68071         me.snapshot = me.snapshot || me.data.clone();
68072         me.data = me.queryBy(fn, scope || me);
68073         me.fireEvent('datachanged', me);
68074     },
68075
68076     /**
68077      * Query the cached records in this Store using a filtering function. The specified function
68078      * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
68079      * included in the results.
68080      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
68081      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
68082      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
68083      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
68084      * </ul>
68085      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
68086      * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
68087      **/
68088     queryBy: function(fn, scope) {
68089         var me = this,
68090         data = me.snapshot || me.data;
68091         return data.filterBy(fn, scope || me);
68092     },
68093
68094     /**
68095      * Loads an array of data straight into the Store.
68096      * 
68097      * Using this method is great if the data is in the correct format already (e.g. it doesn't need to be
68098      * processed by a reader). If your data requires processing to decode the data structure, use a
68099      * {@link Ext.data.proxy.Memory MemoryProxy} instead.
68100      * 
68101      * @param {Ext.data.Model[]/Object[]} data Array of data to load. Any non-model instances will be cast
68102      * into model instances.
68103      * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
68104      * to remove the old ones first.
68105      */
68106     loadData: function(data, append) {
68107         var model = this.model,
68108             length = data.length,
68109             newData = [],
68110             i,
68111             record;
68112
68113         //make sure each data element is an Ext.data.Model instance
68114         for (i = 0; i < length; i++) {
68115             record = data[i];
68116
68117             if (!(record instanceof Ext.data.Model)) {
68118                 record = Ext.ModelManager.create(record, model);
68119             }
68120             newData.push(record);
68121         }
68122
68123         this.loadRecords(newData, {addRecords: append});
68124     },
68125
68126
68127     /**
68128      * Loads data via the bound Proxy's reader
68129      *
68130      * Use this method if you are attempting to load data and want to utilize the configured data reader.
68131      *
68132      * @param {Object[]} data The full JSON object you'd like to load into the Data store.
68133      * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
68134      * to remove the old ones first.
68135      */
68136     loadRawData : function(data, append) {
68137          var me      = this,
68138              result  = me.proxy.reader.read(data),
68139              records = result.records;
68140
68141          if (result.success) {
68142              me.loadRecords(records, { addRecords: append });
68143              me.fireEvent('load', me, records, true);
68144          }
68145      },
68146
68147
68148     /**
68149      * Loads an array of {@link Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
68150      * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #add} instead
68151      * @param {Ext.data.Model[]} records The array of records to load
68152      * @param {Object} options {addRecords: true} to add these records to the existing records, false to remove the Store's existing records first
68153      */
68154     loadRecords: function(records, options) {
68155         var me     = this,
68156             i      = 0,
68157             length = records.length;
68158
68159         options = options || {};
68160
68161
68162         if (!options.addRecords) {
68163             delete me.snapshot;
68164             me.clearData();
68165         }
68166
68167         me.data.addAll(records);
68168
68169         //FIXME: this is not a good solution. Ed Spencer is totally responsible for this and should be forced to fix it immediately.
68170         for (; i < length; i++) {
68171             if (options.start !== undefined) {
68172                 records[i].index = options.start + i;
68173
68174             }
68175             records[i].join(me);
68176         }
68177
68178         /*
68179          * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
68180          * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
68181          * datachanged event is fired by the call to this.add, above.
68182          */
68183         me.suspendEvents();
68184
68185         if (me.filterOnLoad && !me.remoteFilter) {
68186             me.filter();
68187         }
68188
68189         if (me.sortOnLoad && !me.remoteSort) {
68190             me.sort();
68191         }
68192
68193         me.resumeEvents();
68194         me.fireEvent('datachanged', me, records);
68195     },
68196
68197     // PAGING METHODS
68198     /**
68199      * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
68200      * load operation, passing in calculated 'start' and 'limit' params
68201      * @param {Number} page The number of the page to load
68202      * @param {Object} options See options for {@link #load}
68203      */
68204     loadPage: function(page, options) {
68205         var me = this;
68206         options = Ext.apply({}, options);
68207
68208         me.currentPage = page;
68209
68210         me.read(Ext.applyIf(options, {
68211             page: page,
68212             start: (page - 1) * me.pageSize,
68213             limit: me.pageSize,
68214             addRecords: !me.clearOnPageLoad
68215         }));
68216     },
68217
68218     /**
68219      * Loads the next 'page' in the current data set
68220      * @param {Object} options See options for {@link #load}
68221      */
68222     nextPage: function(options) {
68223         this.loadPage(this.currentPage + 1, options);
68224     },
68225
68226     /**
68227      * Loads the previous 'page' in the current data set
68228      * @param {Object} options See options for {@link #load}
68229      */
68230     previousPage: function(options) {
68231         this.loadPage(this.currentPage - 1, options);
68232     },
68233
68234     // private
68235     clearData: function() {
68236         var me = this;
68237         me.data.each(function(record) {
68238             record.unjoin(me);
68239         });
68240
68241         me.data.clear();
68242     },
68243
68244     // Buffering
68245     /**
68246      * Prefetches data into the store using its configured {@link #proxy}.
68247      * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
68248      * See {@link #load}
68249      */
68250     prefetch: function(options) {
68251         var me = this,
68252             operation,
68253             requestId = me.getRequestId();
68254
68255         options = options || {};
68256
68257         Ext.applyIf(options, {
68258             action : 'read',
68259             filters: me.filters.items,
68260             sorters: me.sorters.items,
68261             requestId: requestId
68262         });
68263         me.pendingRequests.push(requestId);
68264
68265         operation = Ext.create('Ext.data.Operation', options);
68266
68267         // HACK to implement loadMask support.
68268         //if (operation.blocking) {
68269         //    me.fireEvent('beforeload', me, operation);
68270         //}
68271         if (me.fireEvent('beforeprefetch', me, operation) !== false) {
68272             me.loading = true;
68273             me.proxy.read(operation, me.onProxyPrefetch, me);
68274         }
68275
68276         return me;
68277     },
68278
68279     /**
68280      * Prefetches a page of data.
68281      * @param {Number} page The page to prefetch
68282      * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
68283      * See {@link #load}
68284      */
68285     prefetchPage: function(page, options) {
68286         var me = this,
68287             pageSize = me.pageSize,
68288             start = (page - 1) * me.pageSize,
68289             end = start + pageSize;
68290
68291         // Currently not requesting this page and range isn't already satisified
68292         if (Ext.Array.indexOf(me.pagesRequested, page) === -1 && !me.rangeSatisfied(start, end)) {
68293             options = options || {};
68294             me.pagesRequested.push(page);
68295             Ext.applyIf(options, {
68296                 page : page,
68297                 start: start,
68298                 limit: pageSize,
68299                 callback: me.onWaitForGuarantee,
68300                 scope: me
68301             });
68302
68303             me.prefetch(options);
68304         }
68305
68306     },
68307
68308     /**
68309      * Returns a unique requestId to track requests.
68310      * @private
68311      */
68312     getRequestId: function() {
68313         this.requestSeed = this.requestSeed || 1;
68314         return this.requestSeed++;
68315     },
68316
68317     /**
68318      * Called after the configured proxy completes a prefetch operation.
68319      * @private
68320      * @param {Ext.data.Operation} operation The operation that completed
68321      */
68322     onProxyPrefetch: function(operation) {
68323         var me         = this,
68324             resultSet  = operation.getResultSet(),
68325             records    = operation.getRecords(),
68326
68327             successful = operation.wasSuccessful();
68328
68329         if (resultSet) {
68330             me.totalCount = resultSet.total;
68331             me.fireEvent('totalcountchange', me.totalCount);
68332         }
68333
68334         if (successful) {
68335             me.cacheRecords(records, operation);
68336         }
68337         Ext.Array.remove(me.pendingRequests, operation.requestId);
68338         if (operation.page) {
68339             Ext.Array.remove(me.pagesRequested, operation.page);
68340         }
68341
68342         me.loading = false;
68343         me.fireEvent('prefetch', me, records, successful, operation);
68344
68345         // HACK to support loadMask
68346         if (operation.blocking) {
68347             me.fireEvent('load', me, records, successful);
68348         }
68349
68350         //this is a callback that would have been passed to the 'read' function and is optional
68351         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
68352     },
68353
68354     /**
68355      * Caches the records in the prefetch and stripes them with their server-side
68356      * index.
68357      * @private
68358      * @param {Ext.data.Model[]} records The records to cache
68359      * @param {Ext.data.Operation} The associated operation
68360      */
68361     cacheRecords: function(records, operation) {
68362         var me     = this,
68363             i      = 0,
68364             length = records.length,
68365             start  = operation ? operation.start : 0;
68366
68367         if (!Ext.isDefined(me.totalCount)) {
68368             me.totalCount = records.length;
68369             me.fireEvent('totalcountchange', me.totalCount);
68370         }
68371
68372         for (; i < length; i++) {
68373             // this is the true index, not the viewIndex
68374             records[i].index = start + i;
68375         }
68376
68377         me.prefetchData.addAll(records);
68378         if (me.purgePageCount) {
68379             me.purgeRecords();
68380         }
68381
68382     },
68383
68384
68385     /**
68386      * Purge the least recently used records in the prefetch if the purgeCount
68387      * has been exceeded.
68388      */
68389     purgeRecords: function() {
68390         var me = this,
68391             prefetchCount = me.prefetchData.getCount(),
68392             purgeCount = me.purgePageCount * me.pageSize,
68393             numRecordsToPurge = prefetchCount - purgeCount - 1,
68394             i = 0;
68395
68396         for (; i <= numRecordsToPurge; i++) {
68397             me.prefetchData.removeAt(0);
68398         }
68399     },
68400
68401     /**
68402      * Determines if the range has already been satisfied in the prefetchData.
68403      * @private
68404      * @param {Number} start The start index
68405      * @param {Number} end The end index in the range
68406      */
68407     rangeSatisfied: function(start, end) {
68408         var me = this,
68409             i = start,
68410             satisfied = true;
68411
68412         for (; i < end; i++) {
68413             if (!me.prefetchData.getByKey(i)) {
68414                 satisfied = false;
68415                 if (end - i > me.pageSize) {
68416                     Ext.Error.raise("A single page prefetch could never satisfy this request.");
68417                 }
68418                 break;
68419             }
68420         }
68421         return satisfied;
68422     },
68423
68424     /**
68425      * Determines the page from a record index
68426      * @param {Number} index The record index
68427      * @return {Number} The page the record belongs to
68428      */
68429     getPageFromRecordIndex: function(index) {
68430         return Math.floor(index / this.pageSize) + 1;
68431     },
68432
68433     /**
68434      * Handles a guaranteed range being loaded
68435      * @private
68436      */
68437     onGuaranteedRange: function() {
68438         var me = this,
68439             totalCount = me.getTotalCount(),
68440             start = me.requestStart,
68441             end = ((totalCount - 1) < me.requestEnd) ? totalCount - 1 : me.requestEnd,
68442             range = [],
68443             record,
68444             i = start;
68445
68446         end = Math.max(0, end);
68447
68448         if (start > end) {
68449             Ext.log({
68450                 level: 'warn',
68451                 msg: 'Start (' + start + ') was greater than end (' + end +
68452                     ') for the range of records requested (' + me.requestStart + '-' +
68453                     me.requestEnd + ')' + (this.storeId ? ' from store "' + this.storeId + '"' : '')
68454             });
68455         }
68456
68457         if (start !== me.guaranteedStart && end !== me.guaranteedEnd) {
68458             me.guaranteedStart = start;
68459             me.guaranteedEnd = end;
68460
68461             for (; i <= end; i++) {
68462                 record = me.prefetchData.getByKey(i);
68463 //                if (!record) {
68464 //                    Ext.log('Record with key "' + i + '" was not found and store said it was guaranteed');
68465 //                }
68466                 if (record) {
68467                     range.push(record);
68468                 }
68469             }
68470             me.fireEvent('guaranteedrange', range, start, end);
68471             if (me.cb) {
68472                 me.cb.call(me.scope || me, range);
68473             }
68474         }
68475
68476         me.unmask();
68477     },
68478
68479     // hack to support loadmask
68480     mask: function() {
68481         this.masked = true;
68482         this.fireEvent('beforeload');
68483     },
68484
68485     // hack to support loadmask
68486     unmask: function() {
68487         if (this.masked) {
68488             this.fireEvent('load');
68489         }
68490     },
68491
68492     /**
68493      * Returns the number of pending requests out.
68494      */
68495     hasPendingRequests: function() {
68496         return this.pendingRequests.length;
68497     },
68498
68499
68500     // wait until all requests finish, until guaranteeing the range.
68501     onWaitForGuarantee: function() {
68502         if (!this.hasPendingRequests()) {
68503             this.onGuaranteedRange();
68504         }
68505     },
68506
68507     /**
68508      * Guarantee a specific range, this will load the store with a range (that
68509      * must be the pageSize or smaller) and take care of any loading that may
68510      * be necessary.
68511      */
68512     guaranteeRange: function(start, end, cb, scope) {
68513         if (start && end) {
68514             if (end - start > this.pageSize) {
68515                 Ext.Error.raise({
68516                     start: start,
68517                     end: end,
68518                     pageSize: this.pageSize,
68519                     msg: "Requested a bigger range than the specified pageSize"
68520                 });
68521             }
68522         }
68523
68524         end = (end > this.totalCount) ? this.totalCount - 1 : end;
68525
68526         var me = this,
68527             i = start,
68528             prefetchData = me.prefetchData,
68529             range = [],
68530             startLoaded = !!prefetchData.getByKey(start),
68531             endLoaded = !!prefetchData.getByKey(end),
68532             startPage = me.getPageFromRecordIndex(start),
68533             endPage = me.getPageFromRecordIndex(end);
68534
68535         me.cb = cb;
68536         me.scope = scope;
68537
68538         me.requestStart = start;
68539         me.requestEnd = end;
68540         // neither beginning or end are loaded
68541         if (!startLoaded || !endLoaded) {
68542             // same page, lets load it
68543             if (startPage === endPage) {
68544                 me.mask();
68545                 me.prefetchPage(startPage, {
68546                     //blocking: true,
68547                     callback: me.onWaitForGuarantee,
68548                     scope: me
68549                 });
68550             // need to load two pages
68551             } else {
68552                 me.mask();
68553                 me.prefetchPage(startPage, {
68554                     //blocking: true,
68555                     callback: me.onWaitForGuarantee,
68556                     scope: me
68557                 });
68558                 me.prefetchPage(endPage, {
68559                     //blocking: true,
68560                     callback: me.onWaitForGuarantee,
68561                     scope: me
68562                 });
68563             }
68564         // Request was already satisfied via the prefetch
68565         } else {
68566             me.onGuaranteedRange();
68567         }
68568     },
68569
68570     // because prefetchData is stored by index
68571     // this invalidates all of the prefetchedData
68572     sort: function() {
68573         var me = this,
68574             prefetchData = me.prefetchData,
68575             sorters,
68576             start,
68577             end,
68578             range;
68579
68580         if (me.buffered) {
68581             if (me.remoteSort) {
68582                 prefetchData.clear();
68583                 me.callParent(arguments);
68584             } else {
68585                 sorters = me.getSorters();
68586                 start = me.guaranteedStart;
68587                 end = me.guaranteedEnd;
68588
68589                 if (sorters.length) {
68590                     prefetchData.sort(sorters);
68591                     range = prefetchData.getRange();
68592                     prefetchData.clear();
68593                     me.cacheRecords(range);
68594                     delete me.guaranteedStart;
68595                     delete me.guaranteedEnd;
68596                     me.guaranteeRange(start, end);
68597                 }
68598                 me.callParent(arguments);
68599             }
68600         } else {
68601             me.callParent(arguments);
68602         }
68603     },
68604
68605     // overriden to provide striping of the indexes as sorting occurs.
68606     // this cannot be done inside of sort because datachanged has already
68607     // fired and will trigger a repaint of the bound view.
68608     doSort: function(sorterFn) {
68609         var me = this;
68610         if (me.remoteSort) {
68611             //the load function will pick up the new sorters and request the sorted data from the proxy
68612             me.load();
68613         } else {
68614             me.data.sortBy(sorterFn);
68615             if (!me.buffered) {
68616                 var range = me.getRange(),
68617                     ln = range.length,
68618                     i  = 0;
68619                 for (; i < ln; i++) {
68620                     range[i].index = i;
68621                 }
68622             }
68623             me.fireEvent('datachanged', me);
68624         }
68625     },
68626
68627     /**
68628      * Finds the index of the first matching Record in this store by a specific field value.
68629      * @param {String} fieldName The name of the Record field to test.
68630      * @param {String/RegExp} value Either a string that the field value
68631      * should begin with, or a RegExp to test against the field.
68632      * @param {Number} startIndex (optional) The index to start searching at
68633      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
68634      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
68635      * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
68636      * @return {Number} The matched index or -1
68637      */
68638     find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
68639         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
68640         return fn ? this.data.findIndexBy(fn, null, start) : -1;
68641     },
68642
68643     /**
68644      * Finds the first matching Record in this store by a specific field value.
68645      * @param {String} fieldName The name of the Record field to test.
68646      * @param {String/RegExp} value Either a string that the field value
68647      * should begin with, or a RegExp to test against the field.
68648      * @param {Number} startIndex (optional) The index to start searching at
68649      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
68650      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
68651      * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
68652      * @return {Ext.data.Model} The matched record or null
68653      */
68654     findRecord: function() {
68655         var me = this,
68656             index = me.find.apply(me, arguments);
68657         return index !== -1 ? me.getAt(index) : null;
68658     },
68659
68660     /**
68661      * @private
68662      * Returns a filter function used to test a the given property's value. Defers most of the work to
68663      * Ext.util.MixedCollection's createValueMatcher function
68664      * @param {String} property The property to create the filter function for
68665      * @param {String/RegExp} value The string/regex to compare the property value to
68666      * @param {Boolean} [anyMatch=false] True if we don't care if the filter value is not the full value.
68667      * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
68668      * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
68669      * Ignored if anyMatch is true.
68670      */
68671     createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
68672         if (Ext.isEmpty(value)) {
68673             return false;
68674         }
68675         value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
68676         return function(r) {
68677             return value.test(r.data[property]);
68678         };
68679     },
68680
68681     /**
68682      * Finds the index of the first matching Record in this store by a specific field value.
68683      * @param {String} fieldName The name of the Record field to test.
68684      * @param {Object} value The value to match the field against.
68685      * @param {Number} startIndex (optional) The index to start searching at
68686      * @return {Number} The matched index or -1
68687      */
68688     findExact: function(property, value, start) {
68689         return this.data.findIndexBy(function(rec) {
68690             return rec.get(property) == value;
68691         },
68692         this, start);
68693     },
68694
68695     /**
68696      * Find the index of the first matching Record in this Store by a function.
68697      * If the function returns <tt>true</tt> it is considered a match.
68698      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
68699      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
68700      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
68701      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
68702      * </ul>
68703      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
68704      * @param {Number} startIndex (optional) The index to start searching at
68705      * @return {Number} The matched index or -1
68706      */
68707     findBy: function(fn, scope, start) {
68708         return this.data.findIndexBy(fn, scope, start);
68709     },
68710
68711     /**
68712      * Collects unique values for a particular dataIndex from this store.
68713      * @param {String} dataIndex The property to collect
68714      * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
68715      * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
68716      * @return {Object[]} An array of the unique values
68717      **/
68718     collect: function(dataIndex, allowNull, bypassFilter) {
68719         var me = this,
68720             data = (bypassFilter === true && me.snapshot) ? me.snapshot: me.data;
68721
68722         return data.collect(dataIndex, 'data', allowNull);
68723     },
68724
68725     /**
68726      * Gets the number of cached records.
68727      * <p>If using paging, this may not be the total size of the dataset. If the data object
68728      * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
68729      * the dataset size.  <b>Note</b>: see the Important note in {@link #load}.</p>
68730      * @return {Number} The number of Records in the Store's cache.
68731      */
68732     getCount: function() {
68733         return this.data.length || 0;
68734     },
68735
68736     /**
68737      * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
68738      * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
68739      * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
68740      * could be loaded into the Store if the Store contained all data
68741      * @return {Number} The total number of Model instances available via the Proxy
68742      */
68743     getTotalCount: function() {
68744         return this.totalCount;
68745     },
68746
68747     /**
68748      * Get the Record at the specified index.
68749      * @param {Number} index The index of the Record to find.
68750      * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
68751      */
68752     getAt: function(index) {
68753         return this.data.getAt(index);
68754     },
68755
68756     /**
68757      * Returns a range of Records between specified indices.
68758      * @param {Number} [startIndex=0] The starting index
68759      * @param {Number} [endIndex] The ending index. Defaults to the last Record in the Store.
68760      * @return {Ext.data.Model[]} An array of Records
68761      */
68762     getRange: function(start, end) {
68763         return this.data.getRange(start, end);
68764     },
68765
68766     /**
68767      * Get the Record with the specified id.
68768      * @param {String} id The id of the Record to find.
68769      * @return {Ext.data.Model} The Record with the passed id. Returns null if not found.
68770      */
68771     getById: function(id) {
68772         return (this.snapshot || this.data).findBy(function(record) {
68773             return record.getId() === id;
68774         });
68775     },
68776
68777     /**
68778      * Get the index within the cache of the passed Record.
68779      * @param {Ext.data.Model} record The Ext.data.Model object to find.
68780      * @return {Number} The index of the passed Record. Returns -1 if not found.
68781      */
68782     indexOf: function(record) {
68783         return this.data.indexOf(record);
68784     },
68785
68786
68787     /**
68788      * Get the index within the entire dataset. From 0 to the totalCount.
68789      * @param {Ext.data.Model} record The Ext.data.Model object to find.
68790      * @return {Number} The index of the passed Record. Returns -1 if not found.
68791      */
68792     indexOfTotal: function(record) {
68793         var index = record.index;
68794         if (index || index === 0) {
68795             return index;
68796         }
68797         return this.indexOf(record);
68798     },
68799
68800     /**
68801      * Get the index within the cache of the Record with the passed id.
68802      * @param {String} id The id of the Record to find.
68803      * @return {Number} The index of the Record. Returns -1 if not found.
68804      */
68805     indexOfId: function(id) {
68806         return this.indexOf(this.getById(id));
68807     },
68808
68809     /**
68810      * Remove all items from the store.
68811      * @param {Boolean} silent Prevent the `clear` event from being fired.
68812      */
68813     removeAll: function(silent) {
68814         var me = this;
68815
68816         me.clearData();
68817         if (me.snapshot) {
68818             me.snapshot.clear();
68819         }
68820         if (silent !== true) {
68821             me.fireEvent('clear', me);
68822         }
68823     },
68824
68825     /*
68826      * Aggregation methods
68827      */
68828
68829     /**
68830      * Convenience function for getting the first model instance in the store
68831      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68832      * in the store. The value returned will be an object literal with the key being the group
68833      * name and the first record being the value. The grouped parameter is only honored if
68834      * the store has a groupField.
68835      * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
68836      */
68837     first: function(grouped) {
68838         var me = this;
68839
68840         if (grouped && me.isGrouped()) {
68841             return me.aggregate(function(records) {
68842                 return records.length ? records[0] : undefined;
68843             }, me, true);
68844         } else {
68845             return me.data.first();
68846         }
68847     },
68848
68849     /**
68850      * Convenience function for getting the last model instance in the store
68851      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68852      * in the store. The value returned will be an object literal with the key being the group
68853      * name and the last record being the value. The grouped parameter is only honored if
68854      * the store has a groupField.
68855      * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
68856      */
68857     last: function(grouped) {
68858         var me = this;
68859
68860         if (grouped && me.isGrouped()) {
68861             return me.aggregate(function(records) {
68862                 var len = records.length;
68863                 return len ? records[len - 1] : undefined;
68864             }, me, true);
68865         } else {
68866             return me.data.last();
68867         }
68868     },
68869
68870     /**
68871      * Sums the value of <tt>property</tt> for each {@link Ext.data.Model record} between <tt>start</tt>
68872      * and <tt>end</tt> and returns the result.
68873      * @param {String} field A field in each record
68874      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68875      * in the store. The value returned will be an object literal with the key being the group
68876      * name and the sum for that group being the value. The grouped parameter is only honored if
68877      * the store has a groupField.
68878      * @return {Number} The sum
68879      */
68880     sum: function(field, grouped) {
68881         var me = this;
68882
68883         if (grouped && me.isGrouped()) {
68884             return me.aggregate(me.getSum, me, true, [field]);
68885         } else {
68886             return me.getSum(me.data.items, field);
68887         }
68888     },
68889
68890     // @private, see sum
68891     getSum: function(records, field) {
68892         var total = 0,
68893             i = 0,
68894             len = records.length;
68895
68896         for (; i < len; ++i) {
68897             total += records[i].get(field);
68898         }
68899
68900         return total;
68901     },
68902
68903     /**
68904      * Gets the count of items in the store.
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 count for each group being the value. The grouped parameter is only honored if
68908      * the store has a groupField.
68909      * @return {Number} the count
68910      */
68911     count: function(grouped) {
68912         var me = this;
68913
68914         if (grouped && me.isGrouped()) {
68915             return me.aggregate(function(records) {
68916                 return records.length;
68917             }, me, true);
68918         } else {
68919             return me.getCount();
68920         }
68921     },
68922
68923     /**
68924      * Gets the minimum value in the store.
68925      * @param {String} field The field in each record
68926      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68927      * in the store. The value returned will be an object literal with the key being the group
68928      * name and the minimum in the group being the value. The grouped parameter is only honored if
68929      * the store has a groupField.
68930      * @return {Object} The minimum value, if no items exist, undefined.
68931      */
68932     min: function(field, grouped) {
68933         var me = this;
68934
68935         if (grouped && me.isGrouped()) {
68936             return me.aggregate(me.getMin, me, true, [field]);
68937         } else {
68938             return me.getMin(me.data.items, field);
68939         }
68940     },
68941
68942     // @private, see min
68943     getMin: function(records, field){
68944         var i = 1,
68945             len = records.length,
68946             value, min;
68947
68948         if (len > 0) {
68949             min = records[0].get(field);
68950         }
68951
68952         for (; i < len; ++i) {
68953             value = records[i].get(field);
68954             if (value < min) {
68955                 min = value;
68956             }
68957         }
68958         return min;
68959     },
68960
68961     /**
68962      * Gets the maximum value in the store.
68963      * @param {String} field The field in each record
68964      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68965      * in the store. The value returned will be an object literal with the key being the group
68966      * name and the maximum in the group being the value. The grouped parameter is only honored if
68967      * the store has a groupField.
68968      * @return {Object} The maximum value, if no items exist, undefined.
68969      */
68970     max: function(field, grouped) {
68971         var me = this;
68972
68973         if (grouped && me.isGrouped()) {
68974             return me.aggregate(me.getMax, me, true, [field]);
68975         } else {
68976             return me.getMax(me.data.items, field);
68977         }
68978     },
68979
68980     // @private, see max
68981     getMax: function(records, field) {
68982         var i = 1,
68983             len = records.length,
68984             value,
68985             max;
68986
68987         if (len > 0) {
68988             max = records[0].get(field);
68989         }
68990
68991         for (; i < len; ++i) {
68992             value = records[i].get(field);
68993             if (value > max) {
68994                 max = value;
68995             }
68996         }
68997         return max;
68998     },
68999
69000     /**
69001      * Gets the average value in the store.
69002      * @param {String} field The field in each record
69003      * @param {Boolean} grouped (Optional) True to perform the operation for each group
69004      * in the store. The value returned will be an object literal with the key being the group
69005      * name and the group average being the value. The grouped parameter is only honored if
69006      * the store has a groupField.
69007      * @return {Object} The average value, if no items exist, 0.
69008      */
69009     average: function(field, grouped) {
69010         var me = this;
69011         if (grouped && me.isGrouped()) {
69012             return me.aggregate(me.getAverage, me, true, [field]);
69013         } else {
69014             return me.getAverage(me.data.items, field);
69015         }
69016     },
69017
69018     // @private, see average
69019     getAverage: function(records, field) {
69020         var i = 0,
69021             len = records.length,
69022             sum = 0;
69023
69024         if (records.length > 0) {
69025             for (; i < len; ++i) {
69026                 sum += records[i].get(field);
69027             }
69028             return sum / len;
69029         }
69030         return 0;
69031     },
69032
69033     /**
69034      * Runs the aggregate function for all the records in the store.
69035      * @param {Function} fn The function to execute. The function is called with a single parameter,
69036      * an array of records for that group.
69037      * @param {Object} scope (optional) The scope to execute the function in. Defaults to the store.
69038      * @param {Boolean} grouped (Optional) True to perform the operation for each group
69039      * in the store. The value returned will be an object literal with the key being the group
69040      * name and the group average being the value. The grouped parameter is only honored if
69041      * the store has a groupField.
69042      * @param {Array} args (optional) Any arguments to append to the function call
69043      * @return {Object} An object literal with the group names and their appropriate values.
69044      */
69045     aggregate: function(fn, scope, grouped, args) {
69046         args = args || [];
69047         if (grouped && this.isGrouped()) {
69048             var groups = this.getGroups(),
69049                 i = 0,
69050                 len = groups.length,
69051                 out = {},
69052                 group;
69053
69054             for (; i < len; ++i) {
69055                 group = groups[i];
69056                 out[group.name] = fn.apply(scope || this, [group.children].concat(args));
69057             }
69058             return out;
69059         } else {
69060             return fn.apply(scope || this, [this.data.items].concat(args));
69061         }
69062     }
69063 }, function() {
69064     // A dummy empty store with a fieldless Model defined in it.
69065     // Just for binding to Views which are instantiated with no Store defined.
69066     // They will be able to run and render fine, and be bound to a generated Store later.
69067     Ext.regStore('ext-empty-store', {fields: [], proxy: 'proxy'});
69068 });
69069
69070 /**
69071  * @author Ed Spencer
69072  * @class Ext.data.JsonStore
69073  * @extends Ext.data.Store
69074  * @ignore
69075  *
69076  * <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
69077  * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.</p>
69078  *
69079  * <p>A store configuration would be something like:</p>
69080  *
69081 <pre><code>
69082 var store = new Ext.data.JsonStore({
69083     // store configs
69084     autoDestroy: true,
69085     storeId: 'myStore',
69086
69087     proxy: {
69088         type: 'ajax',
69089         url: 'get-images.php',
69090         reader: {
69091             type: 'json',
69092             root: 'images',
69093             idProperty: 'name'
69094         }
69095     },
69096
69097     //alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
69098     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
69099 });
69100 </code></pre>
69101  *
69102  * <p>This store is configured to consume a returned object of the form:<pre><code>
69103 {
69104     images: [
69105         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
69106         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
69107     ]
69108 }
69109 </code></pre>
69110  *
69111  * <p>An object literal of this form could also be used as the {@link #data} config option.</p>
69112  *
69113  * @xtype jsonstore
69114  */
69115 Ext.define('Ext.data.JsonStore',  {
69116     extend: 'Ext.data.Store',
69117     alias: 'store.json',
69118
69119     /**
69120      * @cfg {Ext.data.DataReader} reader @hide
69121      */
69122     constructor: function(config) {
69123         config = config || {};
69124
69125         Ext.applyIf(config, {
69126             proxy: {
69127                 type  : 'ajax',
69128                 reader: 'json',
69129                 writer: 'json'
69130             }
69131         });
69132
69133         this.callParent([config]);
69134     }
69135 });
69136
69137 /**
69138  * @class Ext.chart.axis.Time
69139  * @extends Ext.chart.axis.Numeric
69140  *
69141  * A type of axis whose units are measured in time values. Use this axis
69142  * for listing dates that you will want to group or dynamically change.
69143  * If you just want to display dates as categories then use the
69144  * Category class for axis instead.
69145  *
69146  * For example:
69147  *
69148  *     axes: [{
69149  *         type: 'Time',
69150  *         position: 'bottom',
69151  *         fields: 'date',
69152  *         title: 'Day',
69153  *         dateFormat: 'M d',
69154  *
69155  *         constrain: true,
69156  *         fromDate: new Date('1/1/11'),
69157  *         toDate: new Date('1/7/11')
69158  *     }]
69159  *
69160  * In this example we're creating a time axis that has as title *Day*.
69161  * The field the axis is bound to is `date`.
69162  * The date format to use to display the text for the axis labels is `M d`
69163  * which is a three letter month abbreviation followed by the day number.
69164  * The time axis will show values for dates between `fromDate` and `toDate`.
69165  * Since `constrain` is set to true all other values for other dates not between
69166  * the fromDate and toDate will not be displayed.
69167  *
69168  */
69169 Ext.define('Ext.chart.axis.Time', {
69170
69171     /* Begin Definitions */
69172
69173     extend: 'Ext.chart.axis.Numeric',
69174
69175     alternateClassName: 'Ext.chart.TimeAxis',
69176
69177     alias: 'axis.time',
69178
69179     requires: ['Ext.data.Store', 'Ext.data.JsonStore'],
69180
69181     /* End Definitions */
69182
69183     /**
69184      * @cfg {String/Boolean} dateFormat
69185      * Indicates the format the date will be rendered on.
69186      * For example: 'M d' will render the dates as 'Jan 30', etc.
69187      * For a list of possible format strings see {@link Ext.Date Date}
69188      */
69189     dateFormat: false,
69190
69191     /**
69192      * @cfg {Date} fromDate The starting date for the time axis.
69193      */
69194     fromDate: false,
69195
69196     /**
69197      * @cfg {Date} toDate The ending date for the time axis.
69198      */
69199     toDate: false,
69200
69201     /**
69202      * @cfg {Array/Boolean} step
69203      * An array with two components: The first is the unit of the step (day, month, year, etc).
69204      * The second one is the number of units for the step (1, 2, etc.).
69205      * Defaults to `[Ext.Date.DAY, 1]`.
69206      */
69207     step: [Ext.Date.DAY, 1],
69208     
69209     /**
69210      * @cfg {Boolean} constrain
69211      * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate.
69212      * If false, the time axis will adapt to the new values by adding/removing steps.
69213      */
69214     constrain: false,
69215
69216     // Avoid roundtoDecimal call in Numeric Axis's constructor
69217     roundToDecimal: false,
69218     
69219     constructor: function (config) {
69220         var me = this, label, f, df;
69221         me.callParent([config]);
69222         label = me.label || {};
69223         df = this.dateFormat;
69224         if (df) {
69225             if (label.renderer) {
69226                 f = label.renderer;
69227                 label.renderer = function(v) {
69228                     v = f(v);
69229                     return Ext.Date.format(new Date(f(v)), df);
69230                 };
69231             } else {
69232                 label.renderer = function(v) {
69233                     return Ext.Date.format(new Date(v >> 0), df);
69234                 };
69235             }
69236         }
69237     },
69238
69239     doConstrain: function () {
69240         var me = this,
69241             store = me.chart.store,
69242             data = [],
69243             series = me.chart.series.items,
69244             math = Math,
69245             mmax = math.max,
69246             mmin = math.min,
69247             fields = me.fields,
69248             ln = fields.length,
69249             range = me.getRange(),
69250             min = range.min, max = range.max, i, l, excludes = [],
69251             value, values, rec, data = [];
69252         for (i = 0, l = series.length; i < l; i++) {
69253             excludes[i] = series[i].__excludes;
69254         }
69255         store.each(function(record) {
69256             for (i = 0; i < ln; i++) {
69257                 if (excludes[i]) {
69258                     continue;
69259                 }
69260                 value = record.get(fields[i]);
69261                 if (+value < +min) return;
69262                 if (+value > +max) return;
69263             }
69264             data.push(record);
69265         })
69266         me.chart.substore = Ext.create('Ext.data.JsonStore', { model: store.model, data: data });
69267     },
69268
69269     // Before rendering, set current default step count to be number of records.
69270     processView: function () {
69271         var me = this;
69272         if (me.fromDate) {
69273             me.minimum = +me.fromDate;
69274         }
69275         if (me.toDate) {
69276             me.maximum = +me.toDate;
69277         }
69278         if (me.constrain) {
69279             me.doConstrain();
69280         }
69281      },
69282
69283     // @private modifies the store and creates the labels for the axes.
69284     calcEnds: function() {
69285         var me = this, range, step = me.step;
69286         if (step) {
69287             range = me.getRange();
69288             range = Ext.draw.Draw.snapEndsByDateAndStep(new Date(range.min), new Date(range.max), Ext.isNumber(step) ? [Date.MILLI, step]: step);
69289             if (me.minimum) {
69290                 range.from = me.minimum;
69291             }
69292             if (me.maximum) {
69293                 range.to = me.maximum;
69294             }
69295             range.step = (range.to - range.from) / range.steps;
69296             return range;
69297         } else {
69298             return me.callParent(arguments);
69299         }
69300     }
69301  });
69302
69303
69304 /**
69305  * @class Ext.chart.series.Series
69306  *
69307  * Series is the abstract class containing the common logic to all chart series. Series includes
69308  * methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling
69309  * mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
69310  *
69311  * ## Listeners
69312  *
69313  * The series class supports listeners via the Observable syntax. Some of these listeners are:
69314  *
69315  *  - `itemmouseup` When the user interacts with a marker.
69316  *  - `itemmousedown` When the user interacts with a marker.
69317  *  - `itemmousemove` When the user iteracts with a marker.
69318  *  - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
69319  *
69320  * For example:
69321  *
69322  *     series: [{
69323  *             type: 'column',
69324  *             axis: 'left',
69325  *             listeners: {
69326  *                     'afterrender': function() {
69327  *                             console('afterrender');
69328  *                     }
69329  *             },
69330  *             xField: 'category',
69331  *             yField: 'data1'
69332  *     }]
69333  */
69334 Ext.define('Ext.chart.series.Series', {
69335
69336     /* Begin Definitions */
69337
69338     mixins: {
69339         observable: 'Ext.util.Observable',
69340         labels: 'Ext.chart.Label',
69341         highlights: 'Ext.chart.Highlight',
69342         tips: 'Ext.chart.Tip',
69343         callouts: 'Ext.chart.Callout'
69344     },
69345
69346     /* End Definitions */
69347
69348     /**
69349      * @cfg {Boolean/Object} highlight
69350      * If set to `true` it will highlight the markers or the series when hovering
69351      * with the mouse. This parameter can also be an object with the same style
69352      * properties you would apply to a {@link Ext.draw.Sprite} to apply custom
69353      * styles to markers and series.
69354      */
69355
69356     /**
69357      * @cfg {Object} tips
69358      * Add tooltips to the visualization's markers. The options for the tips are the
69359      * same configuration used with {@link Ext.tip.ToolTip}. For example:
69360      *
69361      *     tips: {
69362      *       trackMouse: true,
69363      *       width: 140,
69364      *       height: 28,
69365      *       renderer: function(storeItem, item) {
69366      *         this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
69367      *       }
69368      *     },
69369      */
69370
69371     /**
69372      * @cfg {String} type
69373      * The type of series. Set in subclasses.
69374      */
69375     type: null,
69376
69377     /**
69378      * @cfg {String} title
69379      * The human-readable name of the series.
69380      */
69381     title: null,
69382
69383     /**
69384      * @cfg {Boolean} showInLegend
69385      * Whether to show this series in the legend.
69386      */
69387     showInLegend: true,
69388
69389     /**
69390      * @cfg {Function} renderer
69391      * A function that can be overridden to set custom styling properties to each rendered element.
69392      * Passes in (sprite, record, attributes, index, store) to the function.
69393      */
69394     renderer: function(sprite, record, attributes, index, store) {
69395         return attributes;
69396     },
69397
69398     /**
69399      * @cfg {Array} shadowAttributes
69400      * An array with shadow attributes
69401      */
69402     shadowAttributes: null,
69403
69404     //@private triggerdrawlistener flag
69405     triggerAfterDraw: false,
69406
69407     /**
69408      * @cfg {Object} listeners
69409      * An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
69410      *
69411      *  - itemmouseover
69412      *  - itemmouseout
69413      *  - itemmousedown
69414      *  - itemmouseup
69415      */
69416
69417     constructor: function(config) {
69418         var me = this;
69419         if (config) {
69420             Ext.apply(me, config);
69421         }
69422
69423         me.shadowGroups = [];
69424
69425         me.mixins.labels.constructor.call(me, config);
69426         me.mixins.highlights.constructor.call(me, config);
69427         me.mixins.tips.constructor.call(me, config);
69428         me.mixins.callouts.constructor.call(me, config);
69429
69430         me.addEvents({
69431             scope: me,
69432             itemmouseover: true,
69433             itemmouseout: true,
69434             itemmousedown: true,
69435             itemmouseup: true,
69436             mouseleave: true,
69437             afterdraw: true,
69438
69439             /**
69440              * @event titlechange
69441              * Fires when the series title is changed via {@link #setTitle}.
69442              * @param {String} title The new title value
69443              * @param {Number} index The index in the collection of titles
69444              */
69445             titlechange: true
69446         });
69447
69448         me.mixins.observable.constructor.call(me, config);
69449
69450         me.on({
69451             scope: me,
69452             itemmouseover: me.onItemMouseOver,
69453             itemmouseout: me.onItemMouseOut,
69454             mouseleave: me.onMouseLeave
69455         });
69456     },
69457     
69458     /**
69459      * Iterate over each of the records for this series. The default implementation simply iterates
69460      * through the entire data store, but individual series implementations can override this to
69461      * provide custom handling, e.g. adding/removing records.
69462      * @param {Function} fn The function to execute for each record.
69463      * @param {Object} scope Scope for the fn.
69464      */
69465     eachRecord: function(fn, scope) {
69466         var chart = this.chart;
69467         (chart.substore || chart.store).each(fn, scope);
69468     },
69469
69470     /**
69471      * Return the number of records being displayed in this series. Defaults to the number of
69472      * records in the store; individual series implementations can override to provide custom handling.
69473      */
69474     getRecordCount: function() {
69475         var chart = this.chart,
69476             store = chart.substore || chart.store;
69477         return store ? store.getCount() : 0;
69478     },
69479
69480     /**
69481      * Determines whether the series item at the given index has been excluded, i.e. toggled off in the legend.
69482      * @param index
69483      */
69484     isExcluded: function(index) {
69485         var excludes = this.__excludes;
69486         return !!(excludes && excludes[index]);
69487     },
69488
69489     // @private set the bbox and clipBox for the series
69490     setBBox: function(noGutter) {
69491         var me = this,
69492             chart = me.chart,
69493             chartBBox = chart.chartBBox,
69494             gutterX = noGutter ? 0 : chart.maxGutter[0],
69495             gutterY = noGutter ? 0 : chart.maxGutter[1],
69496             clipBox, bbox;
69497
69498         clipBox = {
69499             x: chartBBox.x,
69500             y: chartBBox.y,
69501             width: chartBBox.width,
69502             height: chartBBox.height
69503         };
69504         me.clipBox = clipBox;
69505
69506         bbox = {
69507             x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
69508             y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
69509             width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
69510             height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
69511         };
69512         me.bbox = bbox;
69513     },
69514
69515     // @private set the animation for the sprite
69516     onAnimate: function(sprite, attr) {
69517         var me = this;
69518         sprite.stopAnimation();
69519         if (me.triggerAfterDraw) {
69520             return sprite.animate(Ext.applyIf(attr, me.chart.animate));
69521         } else {
69522             me.triggerAfterDraw = true;
69523             return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
69524                 listeners: {
69525                     'afteranimate': function() {
69526                         me.triggerAfterDraw = false;
69527                         me.fireEvent('afterrender');
69528                     }
69529                 }
69530             }));
69531         }
69532     },
69533
69534     // @private return the gutter.
69535     getGutters: function() {
69536         return [0, 0];
69537     },
69538
69539     // @private wrapper for the itemmouseover event.
69540     onItemMouseOver: function(item) {
69541         var me = this;
69542         if (item.series === me) {
69543             if (me.highlight) {
69544                 me.highlightItem(item);
69545             }
69546             if (me.tooltip) {
69547                 me.showTip(item);
69548             }
69549         }
69550     },
69551
69552     // @private wrapper for the itemmouseout event.
69553     onItemMouseOut: function(item) {
69554         var me = this;
69555         if (item.series === me) {
69556             me.unHighlightItem();
69557             if (me.tooltip) {
69558                 me.hideTip(item);
69559             }
69560         }
69561     },
69562
69563     // @private wrapper for the mouseleave event.
69564     onMouseLeave: function() {
69565         var me = this;
69566         me.unHighlightItem();
69567         if (me.tooltip) {
69568             me.hideTip();
69569         }
69570     },
69571
69572     /**
69573      * For a given x/y point relative to the Surface, find a corresponding item from this
69574      * series, if any.
69575      * @param {Number} x
69576      * @param {Number} y
69577      * @return {Object} An object describing the item, or null if there is no matching item.
69578      * The exact contents of this object will vary by series type, but should always contain the following:
69579      * @return {Ext.chart.series.Series} return.series the Series object to which the item belongs
69580      * @return {Object} return.value the value(s) of the item's data point
69581      * @return {Array} return.point the x/y coordinates relative to the chart box of a single point
69582      * for this data item, which can be used as e.g. a tooltip anchor point.
69583      * @return {Ext.draw.Sprite} return.sprite the item's rendering Sprite.
69584      */
69585     getItemForPoint: function(x, y) {
69586         //if there are no items to query just return null.
69587         if (!this.items || !this.items.length || this.seriesIsHidden) {
69588             return null;
69589         }
69590         var me = this,
69591             items = me.items,
69592             bbox = me.bbox,
69593             item, i, ln;
69594         // Check bounds
69595         if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
69596             return null;
69597         }
69598         for (i = 0, ln = items.length; i < ln; i++) {
69599             if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
69600                 return items[i];
69601             }
69602         }
69603
69604         return null;
69605     },
69606
69607     isItemInPoint: function(x, y, item, i) {
69608         return false;
69609     },
69610
69611     /**
69612      * Hides all the elements in the series.
69613      */
69614     hideAll: function() {
69615         var me = this,
69616             items = me.items,
69617             item, len, i, j, l, sprite, shadows;
69618
69619         me.seriesIsHidden = true;
69620         me._prevShowMarkers = me.showMarkers;
69621
69622         me.showMarkers = false;
69623         //hide all labels
69624         me.hideLabels(0);
69625         //hide all sprites
69626         for (i = 0, len = items.length; i < len; i++) {
69627             item = items[i];
69628             sprite = item.sprite;
69629             if (sprite) {
69630                 sprite.setAttributes({
69631                     hidden: true
69632                 }, true);
69633             }
69634
69635             if (sprite && sprite.shadows) {
69636                 shadows = sprite.shadows;
69637                 for (j = 0, l = shadows.length; j < l; ++j) {
69638                     shadows[j].setAttributes({
69639                         hidden: true
69640                     }, true);
69641                 }
69642             }
69643         }
69644     },
69645
69646     /**
69647      * Shows all the elements in the series.
69648      */
69649     showAll: function() {
69650         var me = this,
69651             prevAnimate = me.chart.animate;
69652         me.chart.animate = false;
69653         me.seriesIsHidden = false;
69654         me.showMarkers = me._prevShowMarkers;
69655         me.drawSeries();
69656         me.chart.animate = prevAnimate;
69657     },
69658
69659     /**
69660      * Returns a string with the color to be used for the series legend item.
69661      */
69662     getLegendColor: function(index) {
69663         var me = this, fill, stroke;
69664         if (me.seriesStyle) {
69665             fill = me.seriesStyle.fill;
69666             stroke = me.seriesStyle.stroke;
69667             if (fill && fill != 'none') {
69668                 return fill;
69669             }
69670             return stroke;
69671         }
69672         return '#000';
69673     },
69674
69675     /**
69676      * Checks whether the data field should be visible in the legend
69677      * @private
69678      * @param {Number} index The index of the current item
69679      */
69680     visibleInLegend: function(index){
69681         var excludes = this.__excludes;
69682         if (excludes) {
69683             return !excludes[index];
69684         }
69685         return !this.seriesIsHidden;
69686     },
69687
69688     /**
69689      * Changes the value of the {@link #title} for the series.
69690      * Arguments can take two forms:
69691      * <ul>
69692      * <li>A single String value: this will be used as the new single title for the series (applies
69693      * to series with only one yField)</li>
69694      * <li>A numeric index and a String value: this will set the title for a single indexed yField.</li>
69695      * </ul>
69696      * @param {Number} index
69697      * @param {String} title
69698      */
69699     setTitle: function(index, title) {
69700         var me = this,
69701             oldTitle = me.title;
69702
69703         if (Ext.isString(index)) {
69704             title = index;
69705             index = 0;
69706         }
69707
69708         if (Ext.isArray(oldTitle)) {
69709             oldTitle[index] = title;
69710         } else {
69711             me.title = title;
69712         }
69713
69714         me.fireEvent('titlechange', title, index);
69715     }
69716 });
69717
69718 /**
69719  * @class Ext.chart.series.Cartesian
69720  * @extends Ext.chart.series.Series
69721  *
69722  * Common base class for series implementations which plot values using x/y coordinates.
69723  */
69724 Ext.define('Ext.chart.series.Cartesian', {
69725
69726     /* Begin Definitions */
69727
69728     extend: 'Ext.chart.series.Series',
69729
69730     alternateClassName: ['Ext.chart.CartesianSeries', 'Ext.chart.CartesianChart'],
69731
69732     /* End Definitions */
69733
69734     /**
69735      * The field used to access the x axis value from the items from the data
69736      * source.
69737      *
69738      * @cfg xField
69739      * @type String
69740      */
69741     xField: null,
69742
69743     /**
69744      * The field used to access the y-axis value from the items from the data
69745      * source.
69746      *
69747      * @cfg yField
69748      * @type String
69749      */
69750     yField: null,
69751
69752     /**
69753      * @cfg {String} axis
69754      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
69755      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
69756      * relative scale will be used.
69757      */
69758     axis: 'left',
69759
69760     getLegendLabels: function() {
69761         var me = this,
69762             labels = [],
69763             combinations = me.combinations;
69764
69765         Ext.each([].concat(me.yField), function(yField, i) {
69766             var title = me.title;
69767             // Use the 'title' config if present, otherwise use the raw yField name
69768             labels.push((Ext.isArray(title) ? title[i] : title) || yField);
69769         });
69770
69771         // Handle yFields combined via legend drag-drop
69772         if (combinations) {
69773             Ext.each(combinations, function(combo) {
69774                 var label0 = labels[combo[0]],
69775                     label1 = labels[combo[1]];
69776                 labels[combo[1]] = label0 + ' & ' + label1;
69777                 labels.splice(combo[0], 1);
69778             });
69779         }
69780
69781         return labels;
69782     },
69783
69784     /**
69785      * @protected Iterates over a given record's values for each of this series's yFields,
69786      * executing a given function for each value. Any yFields that have been combined
69787      * via legend drag-drop will be treated as a single value.
69788      * @param {Ext.data.Model} record
69789      * @param {Function} fn
69790      * @param {Object} scope
69791      */
69792     eachYValue: function(record, fn, scope) {
69793         Ext.each(this.getYValueAccessors(), function(accessor, i) {
69794             fn.call(scope, accessor(record), i);
69795         });
69796     },
69797
69798     /**
69799      * @protected Returns the number of yField values, taking into account fields combined
69800      * via legend drag-drop.
69801      * @return {Number}
69802      */
69803     getYValueCount: function() {
69804         return this.getYValueAccessors().length;
69805     },
69806
69807     combine: function(index1, index2) {
69808         var me = this,
69809             accessors = me.getYValueAccessors(),
69810             accessor1 = accessors[index1],
69811             accessor2 = accessors[index2];
69812
69813         // Combine the yValue accessors for the two indexes into a single accessor that returns their sum
69814         accessors[index2] = function(record) {
69815             return accessor1(record) + accessor2(record);
69816         };
69817         accessors.splice(index1, 1);
69818
69819         me.callParent([index1, index2]);
69820     },
69821
69822     clearCombinations: function() {
69823         // Clear combined accessors, they'll get regenerated on next call to getYValueAccessors
69824         delete this.yValueAccessors;
69825         this.callParent();
69826     },
69827
69828     /**
69829      * @protected Returns an array of functions, each of which returns the value of the yField
69830      * corresponding to function's index in the array, for a given record (each function takes the
69831      * record as its only argument.) If yFields have been combined by the user via legend drag-drop,
69832      * this list of accessors will be kept in sync with those combinations.
69833      * @return {Array} array of accessor functions
69834      */
69835     getYValueAccessors: function() {
69836         var me = this,
69837             accessors = me.yValueAccessors;
69838         if (!accessors) {
69839             accessors = me.yValueAccessors = [];
69840             Ext.each([].concat(me.yField), function(yField) {
69841                 accessors.push(function(record) {
69842                     return record.get(yField);
69843                 });
69844             });
69845         }
69846         return accessors;
69847     },
69848
69849     /**
69850      * Calculate the min and max values for this series's xField.
69851      * @return {Array} [min, max]
69852      */
69853     getMinMaxXValues: function() {
69854         var me = this,
69855             min, max,
69856             xField = me.xField;
69857
69858         if (me.getRecordCount() > 0) {
69859             min = Infinity;
69860             max = -min;
69861             me.eachRecord(function(record) {
69862                 var xValue = record.get(xField);
69863                 if (xValue > max) {
69864                     max = xValue;
69865                 }
69866                 if (xValue < min) {
69867                     min = xValue;
69868                 }
69869             });
69870         } else {
69871             min = max = 0;
69872         }
69873         return [min, max];
69874     },
69875
69876     /**
69877      * Calculate the min and max values for this series's yField(s). Takes into account yField
69878      * combinations, exclusions, and stacking.
69879      * @return {Array} [min, max]
69880      */
69881     getMinMaxYValues: function() {
69882         var me = this,
69883             stacked = me.stacked,
69884             min, max,
69885             positiveTotal, negativeTotal;
69886
69887         function eachYValueStacked(yValue, i) {
69888             if (!me.isExcluded(i)) {
69889                 if (yValue < 0) {
69890                     negativeTotal += yValue;
69891                 } else {
69892                     positiveTotal += yValue;
69893                 }
69894             }
69895         }
69896
69897         function eachYValue(yValue, i) {
69898             if (!me.isExcluded(i)) {
69899                 if (yValue > max) {
69900                     max = yValue;
69901                 }
69902                 if (yValue < min) {
69903                     min = yValue;
69904                 }
69905             }
69906         }
69907
69908         if (me.getRecordCount() > 0) {
69909             min = Infinity;
69910             max = -min;
69911             me.eachRecord(function(record) {
69912                 if (stacked) {
69913                     positiveTotal = 0;
69914                     negativeTotal = 0;
69915                     me.eachYValue(record, eachYValueStacked);
69916                     if (positiveTotal > max) {
69917                         max = positiveTotal;
69918                     }
69919                     if (negativeTotal < min) {
69920                         min = negativeTotal;
69921                     }
69922                 } else {
69923                     me.eachYValue(record, eachYValue);
69924                 }
69925             });
69926         } else {
69927             min = max = 0;
69928         }
69929         return [min, max];
69930     },
69931
69932     getAxesForXAndYFields: function() {
69933         var me = this,
69934             axes = me.chart.axes,
69935             axis = [].concat(me.axis),
69936             xAxis, yAxis;
69937
69938         if (Ext.Array.indexOf(axis, 'top') > -1) {
69939             xAxis = 'top';
69940         } else if (Ext.Array.indexOf(axis, 'bottom') > -1) {
69941             xAxis = 'bottom';
69942         } else {
69943             if (axes.get('top')) {
69944                 xAxis = 'top';
69945             } else if (axes.get('bottom')) {
69946                 xAxis = 'bottom';
69947             }
69948         }
69949
69950         if (Ext.Array.indexOf(axis, 'left') > -1) {
69951             yAxis = 'left';
69952         } else if (Ext.Array.indexOf(axis, 'right') > -1) {
69953             yAxis = 'right';
69954         } else {
69955             if (axes.get('left')) {
69956                 yAxis = 'left';
69957             } else if (axes.get('right')) {
69958                 yAxis = 'right';
69959             }
69960         }
69961
69962         return {
69963             xAxis: xAxis,
69964             yAxis: yAxis
69965         };
69966     }
69967
69968
69969 });
69970
69971 /**
69972  * @class Ext.chart.series.Area
69973  * @extends Ext.chart.series.Cartesian
69974  *
69975  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
69976  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
69977  * documentation for more information. A typical configuration object for the area series could be:
69978  *
69979  *     @example
69980  *     var store = Ext.create('Ext.data.JsonStore', {
69981  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
69982  *         data: [
69983  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
69984  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
69985  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
69986  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
69987  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
69988  *         ]
69989  *     });
69990  *
69991  *     Ext.create('Ext.chart.Chart', {
69992  *         renderTo: Ext.getBody(),
69993  *         width: 500,
69994  *         height: 300,
69995  *         store: store,
69996  *         axes: [
69997  *             {
69998  *                 type: 'Numeric',
69999  *                 grid: true,
70000  *                 position: 'left',
70001  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
70002  *                 title: 'Sample Values',
70003  *                 grid: {
70004  *                     odd: {
70005  *                         opacity: 1,
70006  *                         fill: '#ddd',
70007  *                         stroke: '#bbb',
70008  *                         'stroke-width': 1
70009  *                     }
70010  *                 },
70011  *                 minimum: 0,
70012  *                 adjustMinimumByMajorUnit: 0
70013  *             },
70014  *             {
70015  *                 type: 'Category',
70016  *                 position: 'bottom',
70017  *                 fields: ['name'],
70018  *                 title: 'Sample Metrics',
70019  *                 grid: true,
70020  *                 label: {
70021  *                     rotate: {
70022  *                         degrees: 315
70023  *                     }
70024  *                 }
70025  *             }
70026  *         ],
70027  *         series: [{
70028  *             type: 'area',
70029  *             highlight: false,
70030  *             axis: 'left',
70031  *             xField: 'name',
70032  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
70033  *             style: {
70034  *                 opacity: 0.93
70035  *             }
70036  *         }]
70037  *     });
70038  *
70039  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
70040  * 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,
70041  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
70042  * to the style object.
70043  *
70044  * @xtype area
70045  */
70046 Ext.define('Ext.chart.series.Area', {
70047
70048     /* Begin Definitions */
70049
70050     extend: 'Ext.chart.series.Cartesian',
70051
70052     alias: 'series.area',
70053
70054     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
70055
70056     /* End Definitions */
70057
70058     type: 'area',
70059
70060     // @private Area charts are alyways stacked
70061     stacked: true,
70062
70063     /**
70064      * @cfg {Object} style
70065      * Append styling properties to this object for it to override theme properties.
70066      */
70067     style: {},
70068
70069     constructor: function(config) {
70070         this.callParent(arguments);
70071         var me = this,
70072             surface = me.chart.surface,
70073             i, l;
70074         Ext.apply(me, config, {
70075             __excludes: [],
70076             highlightCfg: {
70077                 lineWidth: 3,
70078                 stroke: '#55c',
70079                 opacity: 0.8,
70080                 color: '#f00'
70081             }
70082         });
70083         if (me.highlight) {
70084             me.highlightSprite = surface.add({
70085                 type: 'path',
70086                 path: ['M', 0, 0],
70087                 zIndex: 1000,
70088                 opacity: 0.3,
70089                 lineWidth: 5,
70090                 hidden: true,
70091                 stroke: '#444'
70092             });
70093         }
70094         me.group = surface.getGroup(me.seriesId);
70095     },
70096
70097     // @private Shrinks dataSets down to a smaller size
70098     shrink: function(xValues, yValues, size) {
70099         var len = xValues.length,
70100             ratio = Math.floor(len / size),
70101             i, j,
70102             xSum = 0,
70103             yCompLen = this.areas.length,
70104             ySum = [],
70105             xRes = [],
70106             yRes = [];
70107         //initialize array
70108         for (j = 0; j < yCompLen; ++j) {
70109             ySum[j] = 0;
70110         }
70111         for (i = 0; i < len; ++i) {
70112             xSum += xValues[i];
70113             for (j = 0; j < yCompLen; ++j) {
70114                 ySum[j] += yValues[i][j];
70115             }
70116             if (i % ratio == 0) {
70117                 //push averages
70118                 xRes.push(xSum/ratio);
70119                 for (j = 0; j < yCompLen; ++j) {
70120                     ySum[j] /= ratio;
70121                 }
70122                 yRes.push(ySum);
70123                 //reset sum accumulators
70124                 xSum = 0;
70125                 for (j = 0, ySum = []; j < yCompLen; ++j) {
70126                     ySum[j] = 0;
70127                 }
70128             }
70129         }
70130         return {
70131             x: xRes,
70132             y: yRes
70133         };
70134     },
70135
70136     // @private Get chart and data boundaries
70137     getBounds: function() {
70138         var me = this,
70139             chart = me.chart,
70140             store = chart.getChartStore(),
70141             areas = [].concat(me.yField),
70142             areasLen = areas.length,
70143             xValues = [],
70144             yValues = [],
70145             infinity = Infinity,
70146             minX = infinity,
70147             minY = infinity,
70148             maxX = -infinity,
70149             maxY = -infinity,
70150             math = Math,
70151             mmin = math.min,
70152             mmax = math.max,
70153             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
70154
70155         me.setBBox();
70156         bbox = me.bbox;
70157
70158         // Run through the axis
70159         if (me.axis) {
70160             axis = chart.axes.get(me.axis);
70161             if (axis) {
70162                 out = axis.calcEnds();
70163                 minY = out.from || axis.prevMin;
70164                 maxY = mmax(out.to || axis.prevMax, 0);
70165             }
70166         }
70167
70168         if (me.yField && !Ext.isNumber(minY)) {
70169             axis = Ext.create('Ext.chart.axis.Axis', {
70170                 chart: chart,
70171                 fields: [].concat(me.yField)
70172             });
70173             out = axis.calcEnds();
70174             minY = out.from || axis.prevMin;
70175             maxY = mmax(out.to || axis.prevMax, 0);
70176         }
70177
70178         if (!Ext.isNumber(minY)) {
70179             minY = 0;
70180         }
70181         if (!Ext.isNumber(maxY)) {
70182             maxY = 0;
70183         }
70184
70185         store.each(function(record, i) {
70186             xValue = record.get(me.xField);
70187             yValue = [];
70188             if (typeof xValue != 'number') {
70189                 xValue = i;
70190             }
70191             xValues.push(xValue);
70192             acumY = 0;
70193             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
70194                 areaElem = record.get(areas[areaIndex]);
70195                 if (typeof areaElem == 'number') {
70196                     minY = mmin(minY, areaElem);
70197                     yValue.push(areaElem);
70198                     acumY += areaElem;
70199                 }
70200             }
70201             minX = mmin(minX, xValue);
70202             maxX = mmax(maxX, xValue);
70203             maxY = mmax(maxY, acumY);
70204             yValues.push(yValue);
70205         }, me);
70206
70207         xScale = bbox.width / ((maxX - minX) || 1);
70208         yScale = bbox.height / ((maxY - minY) || 1);
70209
70210         ln = xValues.length;
70211         if ((ln > bbox.width) && me.areas) {
70212             sumValues = me.shrink(xValues, yValues, bbox.width);
70213             xValues = sumValues.x;
70214             yValues = sumValues.y;
70215         }
70216
70217         return {
70218             bbox: bbox,
70219             minX: minX,
70220             minY: minY,
70221             xValues: xValues,
70222             yValues: yValues,
70223             xScale: xScale,
70224             yScale: yScale,
70225             areasLen: areasLen
70226         };
70227     },
70228
70229     // @private Build an array of paths for the chart
70230     getPaths: function() {
70231         var me = this,
70232             chart = me.chart,
70233             store = chart.getChartStore(),
70234             first = true,
70235             bounds = me.getBounds(),
70236             bbox = bounds.bbox,
70237             items = me.items = [],
70238             componentPaths = [],
70239             componentPath,
70240             paths = [],
70241             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
70242
70243         ln = bounds.xValues.length;
70244         // Start the path
70245         for (i = 0; i < ln; i++) {
70246             xValue = bounds.xValues[i];
70247             yValue = bounds.yValues[i];
70248             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
70249             acumY = 0;
70250             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70251                 // Excluded series
70252                 if (me.__excludes[areaIndex]) {
70253                     continue;
70254                 }
70255                 if (!componentPaths[areaIndex]) {
70256                     componentPaths[areaIndex] = [];
70257                 }
70258                 areaElem = yValue[areaIndex];
70259                 acumY += areaElem;
70260                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
70261                 if (!paths[areaIndex]) {
70262                     paths[areaIndex] = ['M', x, y];
70263                     componentPaths[areaIndex].push(['L', x, y]);
70264                 } else {
70265                     paths[areaIndex].push('L', x, y);
70266                     componentPaths[areaIndex].push(['L', x, y]);
70267                 }
70268                 if (!items[areaIndex]) {
70269                     items[areaIndex] = {
70270                         pointsUp: [],
70271                         pointsDown: [],
70272                         series: me
70273                     };
70274                 }
70275                 items[areaIndex].pointsUp.push([x, y]);
70276             }
70277         }
70278
70279         // Close the paths
70280         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70281             // Excluded series
70282             if (me.__excludes[areaIndex]) {
70283                 continue;
70284             }
70285             path = paths[areaIndex];
70286             // Close bottom path to the axis
70287             if (areaIndex == 0 || first) {
70288                 first = false;
70289                 path.push('L', x, bbox.y + bbox.height,
70290                           'L', bbox.x, bbox.y + bbox.height,
70291                           'Z');
70292             }
70293             // Close other paths to the one before them
70294             else {
70295                 componentPath = componentPaths[prevAreaIndex];
70296                 componentPath.reverse();
70297                 path.push('L', x, componentPath[0][2]);
70298                 for (i = 0; i < ln; i++) {
70299                     path.push(componentPath[i][0],
70300                               componentPath[i][1],
70301                               componentPath[i][2]);
70302                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
70303                 }
70304                 path.push('L', bbox.x, path[2], 'Z');
70305             }
70306             prevAreaIndex = areaIndex;
70307         }
70308         return {
70309             paths: paths,
70310             areasLen: bounds.areasLen
70311         };
70312     },
70313
70314     /**
70315      * Draws the series for the current chart.
70316      */
70317     drawSeries: function() {
70318         var me = this,
70319             chart = me.chart,
70320             store = chart.getChartStore(),
70321             surface = chart.surface,
70322             animate = chart.animate,
70323             group = me.group,
70324             endLineStyle = Ext.apply(me.seriesStyle, me.style),
70325             colorArrayStyle = me.colorArrayStyle,
70326             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
70327             areaIndex, areaElem, paths, path, rendererAttributes;
70328
70329         me.unHighlightItem();
70330         me.cleanHighlights();
70331
70332         if (!store || !store.getCount()) {
70333             return;
70334         }
70335
70336         paths = me.getPaths();
70337
70338         if (!me.areas) {
70339             me.areas = [];
70340         }
70341
70342         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
70343             // Excluded series
70344             if (me.__excludes[areaIndex]) {
70345                 continue;
70346             }
70347             if (!me.areas[areaIndex]) {
70348                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
70349                     type: 'path',
70350                     group: group,
70351                     // 'clip-rect': me.clipBox,
70352                     path: paths.paths[areaIndex],
70353                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
70354                     fill: colorArrayStyle[areaIndex % colorArrayLength]
70355                 }, endLineStyle || {}));
70356             }
70357             areaElem = me.areas[areaIndex];
70358             path = paths.paths[areaIndex];
70359             if (animate) {
70360                 //Add renderer to line. There is not a unique record associated with this.
70361                 rendererAttributes = me.renderer(areaElem, false, {
70362                     path: path,
70363                     // 'clip-rect': me.clipBox,
70364                     fill: colorArrayStyle[areaIndex % colorArrayLength],
70365                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
70366                 }, areaIndex, store);
70367                 //fill should not be used here but when drawing the special fill path object
70368                 me.animation = me.onAnimate(areaElem, {
70369                     to: rendererAttributes
70370                 });
70371             } else {
70372                 rendererAttributes = me.renderer(areaElem, false, {
70373                     path: path,
70374                     // 'clip-rect': me.clipBox,
70375                     hidden: false,
70376                     fill: colorArrayStyle[areaIndex % colorArrayLength],
70377                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
70378                 }, areaIndex, store);
70379                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
70380             }
70381         }
70382         me.renderLabels();
70383         me.renderCallouts();
70384     },
70385
70386     // @private
70387     onAnimate: function(sprite, attr) {
70388         sprite.show();
70389         return this.callParent(arguments);
70390     },
70391
70392     // @private
70393     onCreateLabel: function(storeItem, item, i, display) {
70394         var me = this,
70395             group = me.labelsGroup,
70396             config = me.label,
70397             bbox = me.bbox,
70398             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
70399
70400         return me.chart.surface.add(Ext.apply({
70401             'type': 'text',
70402             'text-anchor': 'middle',
70403             'group': group,
70404             'x': item.point[0],
70405             'y': bbox.y + bbox.height / 2
70406         }, endLabelStyle || {}));
70407     },
70408
70409     // @private
70410     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
70411         var me = this,
70412             chart = me.chart,
70413             resizing = chart.resizing,
70414             config = me.label,
70415             format = config.renderer,
70416             field = config.field,
70417             bbox = me.bbox,
70418             x = item.point[0],
70419             y = item.point[1],
70420             bb, width, height;
70421
70422         label.setAttributes({
70423             text: format(storeItem.get(field[index])),
70424             hidden: true
70425         }, true);
70426
70427         bb = label.getBBox();
70428         width = bb.width / 2;
70429         height = bb.height / 2;
70430
70431         x = x - width < bbox.x? bbox.x + width : x;
70432         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
70433         y = y - height < bbox.y? bbox.y + height : y;
70434         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
70435
70436         if (me.chart.animate && !me.chart.resizing) {
70437             label.show(true);
70438             me.onAnimate(label, {
70439                 to: {
70440                     x: x,
70441                     y: y
70442                 }
70443             });
70444         } else {
70445             label.setAttributes({
70446                 x: x,
70447                 y: y
70448             }, true);
70449             if (resizing) {
70450                 me.animation.on('afteranimate', function() {
70451                     label.show(true);
70452                 });
70453             } else {
70454                 label.show(true);
70455             }
70456         }
70457     },
70458
70459     // @private
70460     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
70461         var me = this,
70462             chart = me.chart,
70463             surface = chart.surface,
70464             resizing = chart.resizing,
70465             config = me.callouts,
70466             items = me.items,
70467             prev = (i == 0) ? false : items[i -1].point,
70468             next = (i == items.length -1) ? false : items[i +1].point,
70469             cur = item.point,
70470             dir, norm, normal, a, aprev, anext,
70471             bbox = callout.label.getBBox(),
70472             offsetFromViz = 30,
70473             offsetToSide = 10,
70474             offsetBox = 3,
70475             boxx, boxy, boxw, boxh,
70476             p, clipRect = me.clipRect,
70477             x, y;
70478
70479         //get the right two points
70480         if (!prev) {
70481             prev = cur;
70482         }
70483         if (!next) {
70484             next = cur;
70485         }
70486         a = (next[1] - prev[1]) / (next[0] - prev[0]);
70487         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
70488         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
70489
70490         norm = Math.sqrt(1 + a * a);
70491         dir = [1 / norm, a / norm];
70492         normal = [-dir[1], dir[0]];
70493
70494         //keep the label always on the outer part of the "elbow"
70495         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
70496             normal[0] *= -1;
70497             normal[1] *= -1;
70498         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
70499             normal[0] *= -1;
70500             normal[1] *= -1;
70501         }
70502
70503         //position
70504         x = cur[0] + normal[0] * offsetFromViz;
70505         y = cur[1] + normal[1] * offsetFromViz;
70506
70507         //box position and dimensions
70508         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
70509         boxy = y - bbox.height /2 - offsetBox;
70510         boxw = bbox.width + 2 * offsetBox;
70511         boxh = bbox.height + 2 * offsetBox;
70512
70513         //now check if we're out of bounds and invert the normal vector correspondingly
70514         //this may add new overlaps between labels (but labels won't be out of bounds).
70515         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
70516             normal[0] *= -1;
70517         }
70518         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
70519             normal[1] *= -1;
70520         }
70521
70522         //update positions
70523         x = cur[0] + normal[0] * offsetFromViz;
70524         y = cur[1] + normal[1] * offsetFromViz;
70525
70526         //update box position and dimensions
70527         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
70528         boxy = y - bbox.height /2 - offsetBox;
70529         boxw = bbox.width + 2 * offsetBox;
70530         boxh = bbox.height + 2 * offsetBox;
70531
70532         //set the line from the middle of the pie to the box.
70533         callout.lines.setAttributes({
70534             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
70535         }, true);
70536         //set box position
70537         callout.box.setAttributes({
70538             x: boxx,
70539             y: boxy,
70540             width: boxw,
70541             height: boxh
70542         }, true);
70543         //set text position
70544         callout.label.setAttributes({
70545             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
70546             y: y
70547         }, true);
70548         for (p in callout) {
70549             callout[p].show(true);
70550         }
70551     },
70552
70553     isItemInPoint: function(x, y, item, i) {
70554         var me = this,
70555             pointsUp = item.pointsUp,
70556             pointsDown = item.pointsDown,
70557             abs = Math.abs,
70558             dist = Infinity, p, pln, point;
70559
70560         for (p = 0, pln = pointsUp.length; p < pln; p++) {
70561             point = [pointsUp[p][0], pointsUp[p][1]];
70562             if (dist > abs(x - point[0])) {
70563                 dist = abs(x - point[0]);
70564             } else {
70565                 point = pointsUp[p -1];
70566                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
70567                     item.storeIndex = p -1;
70568                     item.storeField = me.yField[i];
70569                     item.storeItem = me.chart.store.getAt(p -1);
70570                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
70571                     return true;
70572                 } else {
70573                     break;
70574                 }
70575             }
70576         }
70577         return false;
70578     },
70579
70580     /**
70581      * Highlight this entire series.
70582      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
70583      */
70584     highlightSeries: function() {
70585         var area, to, fillColor;
70586         if (this._index !== undefined) {
70587             area = this.areas[this._index];
70588             if (area.__highlightAnim) {
70589                 area.__highlightAnim.paused = true;
70590             }
70591             area.__highlighted = true;
70592             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
70593             area.__prevFill = area.__prevFill || area.attr.fill;
70594             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
70595             fillColor = Ext.draw.Color.fromString(area.__prevFill);
70596             to = {
70597                 lineWidth: (area.__prevLineWidth || 0) + 2
70598             };
70599             if (fillColor) {
70600                 to.fill = fillColor.getLighter(0.2).toString();
70601             }
70602             else {
70603                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
70604             }
70605             if (this.chart.animate) {
70606                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
70607                     target: area,
70608                     to: to
70609                 }, this.chart.animate));
70610             }
70611             else {
70612                 area.setAttributes(to, true);
70613             }
70614         }
70615     },
70616
70617     /**
70618      * UnHighlight this entire series.
70619      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
70620      */
70621     unHighlightSeries: function() {
70622         var area;
70623         if (this._index !== undefined) {
70624             area = this.areas[this._index];
70625             if (area.__highlightAnim) {
70626                 area.__highlightAnim.paused = true;
70627             }
70628             if (area.__highlighted) {
70629                 area.__highlighted = false;
70630                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
70631                     target: area,
70632                     to: {
70633                         fill: area.__prevFill,
70634                         opacity: area.__prevOpacity,
70635                         lineWidth: area.__prevLineWidth
70636                     }
70637                 });
70638             }
70639         }
70640     },
70641
70642     /**
70643      * Highlight the specified item. If no item is provided the whole series will be highlighted.
70644      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70645      */
70646     highlightItem: function(item) {
70647         var me = this,
70648             points, path;
70649         if (!item) {
70650             this.highlightSeries();
70651             return;
70652         }
70653         points = item._points;
70654         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
70655                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
70656         me.highlightSprite.setAttributes({
70657             path: path,
70658             hidden: false
70659         }, true);
70660     },
70661
70662     /**
70663      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
70664      * @param {Object} item Info about the item; same format as returned by #getItemForPoint
70665      */
70666     unHighlightItem: function(item) {
70667         if (!item) {
70668             this.unHighlightSeries();
70669         }
70670
70671         if (this.highlightSprite) {
70672             this.highlightSprite.hide(true);
70673         }
70674     },
70675
70676     // @private
70677     hideAll: function() {
70678         if (!isNaN(this._index)) {
70679             this.__excludes[this._index] = true;
70680             this.areas[this._index].hide(true);
70681             this.drawSeries();
70682         }
70683     },
70684
70685     // @private
70686     showAll: function() {
70687         if (!isNaN(this._index)) {
70688             this.__excludes[this._index] = false;
70689             this.areas[this._index].show(true);
70690             this.drawSeries();
70691         }
70692     },
70693
70694     /**
70695      * Returns the color of the series (to be displayed as color for the series legend item).
70696      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70697      */
70698     getLegendColor: function(index) {
70699         var me = this;
70700         return me.colorArrayStyle[index % me.colorArrayStyle.length];
70701     }
70702 });
70703 /**
70704  * @class Ext.chart.series.Area
70705  * @extends Ext.chart.series.Cartesian
70706  *
70707  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
70708  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
70709  * documentation for more information. A typical configuration object for the area series could be:
70710  *
70711  *     @example
70712  *     var store = Ext.create('Ext.data.JsonStore', {
70713  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
70714  *         data: [
70715  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
70716  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
70717  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
70718  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
70719  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
70720  *         ]
70721  *     });
70722  *
70723  *     Ext.create('Ext.chart.Chart', {
70724  *         renderTo: Ext.getBody(),
70725  *         width: 500,
70726  *         height: 300,
70727  *         store: store,
70728  *         axes: [
70729  *             {
70730  *                 type: 'Numeric',
70731  *                 grid: true,
70732  *                 position: 'left',
70733  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
70734  *                 title: 'Sample Values',
70735  *                 grid: {
70736  *                     odd: {
70737  *                         opacity: 1,
70738  *                         fill: '#ddd',
70739  *                         stroke: '#bbb',
70740  *                         'stroke-width': 1
70741  *                     }
70742  *                 },
70743  *                 minimum: 0,
70744  *                 adjustMinimumByMajorUnit: 0
70745  *             },
70746  *             {
70747  *                 type: 'Category',
70748  *                 position: 'bottom',
70749  *                 fields: ['name'],
70750  *                 title: 'Sample Metrics',
70751  *                 grid: true,
70752  *                 label: {
70753  *                     rotate: {
70754  *                         degrees: 315
70755  *                     }
70756  *                 }
70757  *             }
70758  *         ],
70759  *         series: [{
70760  *             type: 'area',
70761  *             highlight: false,
70762  *             axis: 'left',
70763  *             xField: 'name',
70764  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
70765  *             style: {
70766  *                 opacity: 0.93
70767  *             }
70768  *         }]
70769  *     });
70770  *
70771  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
70772  * 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,
70773  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
70774  * to the style object.
70775  *
70776  * @xtype area
70777  */
70778 Ext.define('Ext.chart.series.Area', {
70779
70780     /* Begin Definitions */
70781
70782     extend: 'Ext.chart.series.Cartesian',
70783
70784     alias: 'series.area',
70785
70786     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
70787
70788     /* End Definitions */
70789
70790     type: 'area',
70791
70792     // @private Area charts are alyways stacked
70793     stacked: true,
70794
70795     /**
70796      * @cfg {Object} style
70797      * Append styling properties to this object for it to override theme properties.
70798      */
70799     style: {},
70800
70801     constructor: function(config) {
70802         this.callParent(arguments);
70803         var me = this,
70804             surface = me.chart.surface,
70805             i, l;
70806         Ext.apply(me, config, {
70807             __excludes: [],
70808             highlightCfg: {
70809                 lineWidth: 3,
70810                 stroke: '#55c',
70811                 opacity: 0.8,
70812                 color: '#f00'
70813             }
70814         });
70815         if (me.highlight) {
70816             me.highlightSprite = surface.add({
70817                 type: 'path',
70818                 path: ['M', 0, 0],
70819                 zIndex: 1000,
70820                 opacity: 0.3,
70821                 lineWidth: 5,
70822                 hidden: true,
70823                 stroke: '#444'
70824             });
70825         }
70826         me.group = surface.getGroup(me.seriesId);
70827     },
70828
70829     // @private Shrinks dataSets down to a smaller size
70830     shrink: function(xValues, yValues, size) {
70831         var len = xValues.length,
70832             ratio = Math.floor(len / size),
70833             i, j,
70834             xSum = 0,
70835             yCompLen = this.areas.length,
70836             ySum = [],
70837             xRes = [],
70838             yRes = [];
70839         //initialize array
70840         for (j = 0; j < yCompLen; ++j) {
70841             ySum[j] = 0;
70842         }
70843         for (i = 0; i < len; ++i) {
70844             xSum += xValues[i];
70845             for (j = 0; j < yCompLen; ++j) {
70846                 ySum[j] += yValues[i][j];
70847             }
70848             if (i % ratio == 0) {
70849                 //push averages
70850                 xRes.push(xSum/ratio);
70851                 for (j = 0; j < yCompLen; ++j) {
70852                     ySum[j] /= ratio;
70853                 }
70854                 yRes.push(ySum);
70855                 //reset sum accumulators
70856                 xSum = 0;
70857                 for (j = 0, ySum = []; j < yCompLen; ++j) {
70858                     ySum[j] = 0;
70859                 }
70860             }
70861         }
70862         return {
70863             x: xRes,
70864             y: yRes
70865         };
70866     },
70867
70868     // @private Get chart and data boundaries
70869     getBounds: function() {
70870         var me = this,
70871             chart = me.chart,
70872             store = chart.getChartStore(),
70873             areas = [].concat(me.yField),
70874             areasLen = areas.length,
70875             xValues = [],
70876             yValues = [],
70877             infinity = Infinity,
70878             minX = infinity,
70879             minY = infinity,
70880             maxX = -infinity,
70881             maxY = -infinity,
70882             math = Math,
70883             mmin = math.min,
70884             mmax = math.max,
70885             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
70886
70887         me.setBBox();
70888         bbox = me.bbox;
70889
70890         // Run through the axis
70891         if (me.axis) {
70892             axis = chart.axes.get(me.axis);
70893             if (axis) {
70894                 out = axis.calcEnds();
70895                 minY = out.from || axis.prevMin;
70896                 maxY = mmax(out.to || axis.prevMax, 0);
70897             }
70898         }
70899
70900         if (me.yField && !Ext.isNumber(minY)) {
70901             axis = Ext.create('Ext.chart.axis.Axis', {
70902                 chart: chart,
70903                 fields: [].concat(me.yField)
70904             });
70905             out = axis.calcEnds();
70906             minY = out.from || axis.prevMin;
70907             maxY = mmax(out.to || axis.prevMax, 0);
70908         }
70909
70910         if (!Ext.isNumber(minY)) {
70911             minY = 0;
70912         }
70913         if (!Ext.isNumber(maxY)) {
70914             maxY = 0;
70915         }
70916
70917         store.each(function(record, i) {
70918             xValue = record.get(me.xField);
70919             yValue = [];
70920             if (typeof xValue != 'number') {
70921                 xValue = i;
70922             }
70923             xValues.push(xValue);
70924             acumY = 0;
70925             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
70926                 areaElem = record.get(areas[areaIndex]);
70927                 if (typeof areaElem == 'number') {
70928                     minY = mmin(minY, areaElem);
70929                     yValue.push(areaElem);
70930                     acumY += areaElem;
70931                 }
70932             }
70933             minX = mmin(minX, xValue);
70934             maxX = mmax(maxX, xValue);
70935             maxY = mmax(maxY, acumY);
70936             yValues.push(yValue);
70937         }, me);
70938
70939         xScale = bbox.width / ((maxX - minX) || 1);
70940         yScale = bbox.height / ((maxY - minY) || 1);
70941
70942         ln = xValues.length;
70943         if ((ln > bbox.width) && me.areas) {
70944             sumValues = me.shrink(xValues, yValues, bbox.width);
70945             xValues = sumValues.x;
70946             yValues = sumValues.y;
70947         }
70948
70949         return {
70950             bbox: bbox,
70951             minX: minX,
70952             minY: minY,
70953             xValues: xValues,
70954             yValues: yValues,
70955             xScale: xScale,
70956             yScale: yScale,
70957             areasLen: areasLen
70958         };
70959     },
70960
70961     // @private Build an array of paths for the chart
70962     getPaths: function() {
70963         var me = this,
70964             chart = me.chart,
70965             store = chart.getChartStore(),
70966             first = true,
70967             bounds = me.getBounds(),
70968             bbox = bounds.bbox,
70969             items = me.items = [],
70970             componentPaths = [],
70971             componentPath,
70972             paths = [],
70973             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
70974
70975         ln = bounds.xValues.length;
70976         // Start the path
70977         for (i = 0; i < ln; i++) {
70978             xValue = bounds.xValues[i];
70979             yValue = bounds.yValues[i];
70980             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
70981             acumY = 0;
70982             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70983                 // Excluded series
70984                 if (me.__excludes[areaIndex]) {
70985                     continue;
70986                 }
70987                 if (!componentPaths[areaIndex]) {
70988                     componentPaths[areaIndex] = [];
70989                 }
70990                 areaElem = yValue[areaIndex];
70991                 acumY += areaElem;
70992                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
70993                 if (!paths[areaIndex]) {
70994                     paths[areaIndex] = ['M', x, y];
70995                     componentPaths[areaIndex].push(['L', x, y]);
70996                 } else {
70997                     paths[areaIndex].push('L', x, y);
70998                     componentPaths[areaIndex].push(['L', x, y]);
70999                 }
71000                 if (!items[areaIndex]) {
71001                     items[areaIndex] = {
71002                         pointsUp: [],
71003                         pointsDown: [],
71004                         series: me
71005                     };
71006                 }
71007                 items[areaIndex].pointsUp.push([x, y]);
71008             }
71009         }
71010
71011         // Close the paths
71012         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
71013             // Excluded series
71014             if (me.__excludes[areaIndex]) {
71015                 continue;
71016             }
71017             path = paths[areaIndex];
71018             // Close bottom path to the axis
71019             if (areaIndex == 0 || first) {
71020                 first = false;
71021                 path.push('L', x, bbox.y + bbox.height,
71022                           'L', bbox.x, bbox.y + bbox.height,
71023                           'Z');
71024             }
71025             // Close other paths to the one before them
71026             else {
71027                 componentPath = componentPaths[prevAreaIndex];
71028                 componentPath.reverse();
71029                 path.push('L', x, componentPath[0][2]);
71030                 for (i = 0; i < ln; i++) {
71031                     path.push(componentPath[i][0],
71032                               componentPath[i][1],
71033                               componentPath[i][2]);
71034                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
71035                 }
71036                 path.push('L', bbox.x, path[2], 'Z');
71037             }
71038             prevAreaIndex = areaIndex;
71039         }
71040         return {
71041             paths: paths,
71042             areasLen: bounds.areasLen
71043         };
71044     },
71045
71046     /**
71047      * Draws the series for the current chart.
71048      */
71049     drawSeries: function() {
71050         var me = this,
71051             chart = me.chart,
71052             store = chart.getChartStore(),
71053             surface = chart.surface,
71054             animate = chart.animate,
71055             group = me.group,
71056             endLineStyle = Ext.apply(me.seriesStyle, me.style),
71057             colorArrayStyle = me.colorArrayStyle,
71058             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
71059             areaIndex, areaElem, paths, path, rendererAttributes;
71060
71061         me.unHighlightItem();
71062         me.cleanHighlights();
71063
71064         if (!store || !store.getCount()) {
71065             return;
71066         }
71067
71068         paths = me.getPaths();
71069
71070         if (!me.areas) {
71071             me.areas = [];
71072         }
71073
71074         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
71075             // Excluded series
71076             if (me.__excludes[areaIndex]) {
71077                 continue;
71078             }
71079             if (!me.areas[areaIndex]) {
71080                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
71081                     type: 'path',
71082                     group: group,
71083                     // 'clip-rect': me.clipBox,
71084                     path: paths.paths[areaIndex],
71085                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
71086                     fill: colorArrayStyle[areaIndex % colorArrayLength]
71087                 }, endLineStyle || {}));
71088             }
71089             areaElem = me.areas[areaIndex];
71090             path = paths.paths[areaIndex];
71091             if (animate) {
71092                 //Add renderer to line. There is not a unique record associated with this.
71093                 rendererAttributes = me.renderer(areaElem, false, {
71094                     path: path,
71095                     // 'clip-rect': me.clipBox,
71096                     fill: colorArrayStyle[areaIndex % colorArrayLength],
71097                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
71098                 }, areaIndex, store);
71099                 //fill should not be used here but when drawing the special fill path object
71100                 me.animation = me.onAnimate(areaElem, {
71101                     to: rendererAttributes
71102                 });
71103             } else {
71104                 rendererAttributes = me.renderer(areaElem, false, {
71105                     path: path,
71106                     // 'clip-rect': me.clipBox,
71107                     hidden: false,
71108                     fill: colorArrayStyle[areaIndex % colorArrayLength],
71109                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
71110                 }, areaIndex, store);
71111                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
71112             }
71113         }
71114         me.renderLabels();
71115         me.renderCallouts();
71116     },
71117
71118     // @private
71119     onAnimate: function(sprite, attr) {
71120         sprite.show();
71121         return this.callParent(arguments);
71122     },
71123
71124     // @private
71125     onCreateLabel: function(storeItem, item, i, display) {
71126         var me = this,
71127             group = me.labelsGroup,
71128             config = me.label,
71129             bbox = me.bbox,
71130             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
71131
71132         return me.chart.surface.add(Ext.apply({
71133             'type': 'text',
71134             'text-anchor': 'middle',
71135             'group': group,
71136             'x': item.point[0],
71137             'y': bbox.y + bbox.height / 2
71138         }, endLabelStyle || {}));
71139     },
71140
71141     // @private
71142     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
71143         var me = this,
71144             chart = me.chart,
71145             resizing = chart.resizing,
71146             config = me.label,
71147             format = config.renderer,
71148             field = config.field,
71149             bbox = me.bbox,
71150             x = item.point[0],
71151             y = item.point[1],
71152             bb, width, height;
71153
71154         label.setAttributes({
71155             text: format(storeItem.get(field[index])),
71156             hidden: true
71157         }, true);
71158
71159         bb = label.getBBox();
71160         width = bb.width / 2;
71161         height = bb.height / 2;
71162
71163         x = x - width < bbox.x? bbox.x + width : x;
71164         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
71165         y = y - height < bbox.y? bbox.y + height : y;
71166         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
71167
71168         if (me.chart.animate && !me.chart.resizing) {
71169             label.show(true);
71170             me.onAnimate(label, {
71171                 to: {
71172                     x: x,
71173                     y: y
71174                 }
71175             });
71176         } else {
71177             label.setAttributes({
71178                 x: x,
71179                 y: y
71180             }, true);
71181             if (resizing) {
71182                 me.animation.on('afteranimate', function() {
71183                     label.show(true);
71184                 });
71185             } else {
71186                 label.show(true);
71187             }
71188         }
71189     },
71190
71191     // @private
71192     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
71193         var me = this,
71194             chart = me.chart,
71195             surface = chart.surface,
71196             resizing = chart.resizing,
71197             config = me.callouts,
71198             items = me.items,
71199             prev = (i == 0) ? false : items[i -1].point,
71200             next = (i == items.length -1) ? false : items[i +1].point,
71201             cur = item.point,
71202             dir, norm, normal, a, aprev, anext,
71203             bbox = callout.label.getBBox(),
71204             offsetFromViz = 30,
71205             offsetToSide = 10,
71206             offsetBox = 3,
71207             boxx, boxy, boxw, boxh,
71208             p, clipRect = me.clipRect,
71209             x, y;
71210
71211         //get the right two points
71212         if (!prev) {
71213             prev = cur;
71214         }
71215         if (!next) {
71216             next = cur;
71217         }
71218         a = (next[1] - prev[1]) / (next[0] - prev[0]);
71219         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
71220         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
71221
71222         norm = Math.sqrt(1 + a * a);
71223         dir = [1 / norm, a / norm];
71224         normal = [-dir[1], dir[0]];
71225
71226         //keep the label always on the outer part of the "elbow"
71227         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
71228             normal[0] *= -1;
71229             normal[1] *= -1;
71230         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
71231             normal[0] *= -1;
71232             normal[1] *= -1;
71233         }
71234
71235         //position
71236         x = cur[0] + normal[0] * offsetFromViz;
71237         y = cur[1] + normal[1] * offsetFromViz;
71238
71239         //box position and dimensions
71240         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
71241         boxy = y - bbox.height /2 - offsetBox;
71242         boxw = bbox.width + 2 * offsetBox;
71243         boxh = bbox.height + 2 * offsetBox;
71244
71245         //now check if we're out of bounds and invert the normal vector correspondingly
71246         //this may add new overlaps between labels (but labels won't be out of bounds).
71247         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
71248             normal[0] *= -1;
71249         }
71250         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
71251             normal[1] *= -1;
71252         }
71253
71254         //update positions
71255         x = cur[0] + normal[0] * offsetFromViz;
71256         y = cur[1] + normal[1] * offsetFromViz;
71257
71258         //update box position and dimensions
71259         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
71260         boxy = y - bbox.height /2 - offsetBox;
71261         boxw = bbox.width + 2 * offsetBox;
71262         boxh = bbox.height + 2 * offsetBox;
71263
71264         //set the line from the middle of the pie to the box.
71265         callout.lines.setAttributes({
71266             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
71267         }, true);
71268         //set box position
71269         callout.box.setAttributes({
71270             x: boxx,
71271             y: boxy,
71272             width: boxw,
71273             height: boxh
71274         }, true);
71275         //set text position
71276         callout.label.setAttributes({
71277             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
71278             y: y
71279         }, true);
71280         for (p in callout) {
71281             callout[p].show(true);
71282         }
71283     },
71284
71285     isItemInPoint: function(x, y, item, i) {
71286         var me = this,
71287             pointsUp = item.pointsUp,
71288             pointsDown = item.pointsDown,
71289             abs = Math.abs,
71290             dist = Infinity, p, pln, point;
71291
71292         for (p = 0, pln = pointsUp.length; p < pln; p++) {
71293             point = [pointsUp[p][0], pointsUp[p][1]];
71294             if (dist > abs(x - point[0])) {
71295                 dist = abs(x - point[0]);
71296             } else {
71297                 point = pointsUp[p -1];
71298                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
71299                     item.storeIndex = p -1;
71300                     item.storeField = me.yField[i];
71301                     item.storeItem = me.chart.store.getAt(p -1);
71302                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
71303                     return true;
71304                 } else {
71305                     break;
71306                 }
71307             }
71308         }
71309         return false;
71310     },
71311
71312     /**
71313      * Highlight this entire series.
71314      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
71315      */
71316     highlightSeries: function() {
71317         var area, to, fillColor;
71318         if (this._index !== undefined) {
71319             area = this.areas[this._index];
71320             if (area.__highlightAnim) {
71321                 area.__highlightAnim.paused = true;
71322             }
71323             area.__highlighted = true;
71324             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
71325             area.__prevFill = area.__prevFill || area.attr.fill;
71326             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
71327             fillColor = Ext.draw.Color.fromString(area.__prevFill);
71328             to = {
71329                 lineWidth: (area.__prevLineWidth || 0) + 2
71330             };
71331             if (fillColor) {
71332                 to.fill = fillColor.getLighter(0.2).toString();
71333             }
71334             else {
71335                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
71336             }
71337             if (this.chart.animate) {
71338                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
71339                     target: area,
71340                     to: to
71341                 }, this.chart.animate));
71342             }
71343             else {
71344                 area.setAttributes(to, true);
71345             }
71346         }
71347     },
71348
71349     /**
71350      * UnHighlight this entire series.
71351      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
71352      */
71353     unHighlightSeries: function() {
71354         var area;
71355         if (this._index !== undefined) {
71356             area = this.areas[this._index];
71357             if (area.__highlightAnim) {
71358                 area.__highlightAnim.paused = true;
71359             }
71360             if (area.__highlighted) {
71361                 area.__highlighted = false;
71362                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
71363                     target: area,
71364                     to: {
71365                         fill: area.__prevFill,
71366                         opacity: area.__prevOpacity,
71367                         lineWidth: area.__prevLineWidth
71368                     }
71369                 });
71370             }
71371         }
71372     },
71373
71374     /**
71375      * Highlight the specified item. If no item is provided the whole series will be highlighted.
71376      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
71377      */
71378     highlightItem: function(item) {
71379         var me = this,
71380             points, path;
71381         if (!item) {
71382             this.highlightSeries();
71383             return;
71384         }
71385         points = item._points;
71386         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
71387                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
71388         me.highlightSprite.setAttributes({
71389             path: path,
71390             hidden: false
71391         }, true);
71392     },
71393
71394     /**
71395      * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
71396      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
71397      */
71398     unHighlightItem: function(item) {
71399         if (!item) {
71400             this.unHighlightSeries();
71401         }
71402
71403         if (this.highlightSprite) {
71404             this.highlightSprite.hide(true);
71405         }
71406     },
71407
71408     // @private
71409     hideAll: function() {
71410         if (!isNaN(this._index)) {
71411             this.__excludes[this._index] = true;
71412             this.areas[this._index].hide(true);
71413             this.drawSeries();
71414         }
71415     },
71416
71417     // @private
71418     showAll: function() {
71419         if (!isNaN(this._index)) {
71420             this.__excludes[this._index] = false;
71421             this.areas[this._index].show(true);
71422             this.drawSeries();
71423         }
71424     },
71425
71426     /**
71427      * Returns the color of the series (to be displayed as color for the series legend item).
71428      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
71429      */
71430     getLegendColor: function(index) {
71431         var me = this;
71432         return me.colorArrayStyle[index % me.colorArrayStyle.length];
71433     }
71434 });
71435
71436 /**
71437  * Creates a Bar Chart. A Bar Chart is a useful visualization technique to display quantitative information for
71438  * different categories that can show some progression (or regression) in the dataset. As with all other series, the Bar
71439  * Series must be appended in the *series* Chart array configuration. See the Chart documentation for more information.
71440  * A typical configuration object for the bar series could be:
71441  *
71442  *     @example
71443  *     var store = Ext.create('Ext.data.JsonStore', {
71444  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
71445  *         data: [
71446  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
71447  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
71448  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
71449  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
71450  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
71451  *         ]
71452  *     });
71453  *
71454  *     Ext.create('Ext.chart.Chart', {
71455  *         renderTo: Ext.getBody(),
71456  *         width: 500,
71457  *         height: 300,
71458  *         animate: true,
71459  *         store: store,
71460  *         axes: [{
71461  *             type: 'Numeric',
71462  *             position: 'bottom',
71463  *             fields: ['data1'],
71464  *             label: {
71465  *                 renderer: Ext.util.Format.numberRenderer('0,0')
71466  *             },
71467  *             title: 'Sample Values',
71468  *             grid: true,
71469  *             minimum: 0
71470  *         }, {
71471  *             type: 'Category',
71472  *             position: 'left',
71473  *             fields: ['name'],
71474  *             title: 'Sample Metrics'
71475  *         }],
71476  *         series: [{
71477  *             type: 'bar',
71478  *             axis: 'bottom',
71479  *             highlight: true,
71480  *             tips: {
71481  *               trackMouse: true,
71482  *               width: 140,
71483  *               height: 28,
71484  *               renderer: function(storeItem, item) {
71485  *                 this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
71486  *               }
71487  *             },
71488  *             label: {
71489  *               display: 'insideEnd',
71490  *                 field: 'data1',
71491  *                 renderer: Ext.util.Format.numberRenderer('0'),
71492  *                 orientation: 'horizontal',
71493  *                 color: '#333',
71494  *                 'text-anchor': 'middle'
71495  *             },
71496  *             xField: 'name',
71497  *             yField: ['data1']
71498  *         }]
71499  *     });
71500  *
71501  * In this configuration we set `bar` as the series type, bind the values of the bar to the bottom axis and set the
71502  * xField or category field to the `name` parameter of the store. We also set `highlight` to true which enables smooth
71503  * animations when bars are hovered. We also set some configuration for the bar labels to be displayed inside the bar,
71504  * to display the information found in the `data1` property of each element store, to render a formated text with the
71505  * `Ext.util.Format` we pass in, to have an `horizontal` orientation (as opposed to a vertical one) and we also set
71506  * other styles like `color`, `text-anchor`, etc.
71507  */
71508 Ext.define('Ext.chart.series.Bar', {
71509
71510     /* Begin Definitions */
71511
71512     extend: 'Ext.chart.series.Cartesian',
71513
71514     alternateClassName: ['Ext.chart.BarSeries', 'Ext.chart.BarChart', 'Ext.chart.StackedBarChart'],
71515
71516     requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
71517
71518     /* End Definitions */
71519
71520     type: 'bar',
71521
71522     alias: 'series.bar',
71523     /**
71524      * @cfg {Boolean} column Whether to set the visualization as column chart or horizontal bar chart.
71525      */
71526     column: false,
71527
71528     /**
71529      * @cfg style Style properties that will override the theming series styles.
71530      */
71531     style: {},
71532
71533     /**
71534      * @cfg {Number} gutter The gutter space between single bars, as a percentage of the bar width
71535      */
71536     gutter: 38.2,
71537
71538     /**
71539      * @cfg {Number} groupGutter The gutter space between groups of bars, as a percentage of the bar width
71540      */
71541     groupGutter: 38.2,
71542
71543     /**
71544      * @cfg {Number} xPadding Padding between the left/right axes and the bars
71545      */
71546     xPadding: 0,
71547
71548     /**
71549      * @cfg {Number} yPadding Padding between the top/bottom axes and the bars
71550      */
71551     yPadding: 10,
71552
71553     constructor: function(config) {
71554         this.callParent(arguments);
71555         var me = this,
71556             surface = me.chart.surface,
71557             shadow = me.chart.shadow,
71558             i, l;
71559         Ext.apply(me, config, {
71560             highlightCfg: {
71561                 lineWidth: 3,
71562                 stroke: '#55c',
71563                 opacity: 0.8,
71564                 color: '#f00'
71565             },
71566
71567             shadowAttributes: [{
71568                 "stroke-width": 6,
71569                 "stroke-opacity": 0.05,
71570                 stroke: 'rgb(200, 200, 200)',
71571                 translate: {
71572                     x: 1.2,
71573                     y: 1.2
71574                 }
71575             }, {
71576                 "stroke-width": 4,
71577                 "stroke-opacity": 0.1,
71578                 stroke: 'rgb(150, 150, 150)',
71579                 translate: {
71580                     x: 0.9,
71581                     y: 0.9
71582                 }
71583             }, {
71584                 "stroke-width": 2,
71585                 "stroke-opacity": 0.15,
71586                 stroke: 'rgb(100, 100, 100)',
71587                 translate: {
71588                     x: 0.6,
71589                     y: 0.6
71590                 }
71591             }]
71592         });
71593         me.group = surface.getGroup(me.seriesId + '-bars');
71594         if (shadow) {
71595             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
71596                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
71597             }
71598         }
71599     },
71600
71601     // @private sets the bar girth.
71602     getBarGirth: function() {
71603         var me = this,
71604             store = me.chart.getChartStore(),
71605             column = me.column,
71606             ln = store.getCount(),
71607             gutter = me.gutter / 100;
71608
71609         return (me.chart.chartBBox[column ? 'width' : 'height'] - me[column ? 'xPadding' : 'yPadding'] * 2) / (ln * (gutter + 1) - gutter);
71610     },
71611
71612     // @private returns the gutters.
71613     getGutters: function() {
71614         var me = this,
71615             column = me.column,
71616             gutter = Math.ceil(me[column ? 'xPadding' : 'yPadding'] + me.getBarGirth() / 2);
71617         return me.column ? [gutter, 0] : [0, gutter];
71618     },
71619
71620     // @private Get chart and data boundaries
71621     getBounds: function() {
71622         var me = this,
71623             chart = me.chart,
71624             store = chart.getChartStore(),
71625             bars = [].concat(me.yField),
71626             barsLen = bars.length,
71627             groupBarsLen = barsLen,
71628             groupGutter = me.groupGutter / 100,
71629             column = me.column,
71630             xPadding = me.xPadding,
71631             yPadding = me.yPadding,
71632             stacked = me.stacked,
71633             barWidth = me.getBarGirth(),
71634             math = Math,
71635             mmax = math.max,
71636             mabs = math.abs,
71637             groupBarWidth, bbox, minY, maxY, axis, out,
71638             scale, zero, total, rec, j, plus, minus;
71639
71640         me.setBBox(true);
71641         bbox = me.bbox;
71642
71643         //Skip excluded series
71644         if (me.__excludes) {
71645             for (j = 0, total = me.__excludes.length; j < total; j++) {
71646                 if (me.__excludes[j]) {
71647                     groupBarsLen--;
71648                 }
71649             }
71650         }
71651
71652         if (me.axis) {
71653             axis = chart.axes.get(me.axis);
71654             if (axis) {
71655                 out = axis.calcEnds();
71656                 minY = out.from;
71657                 maxY = out.to;
71658             }
71659         }
71660
71661         if (me.yField && !Ext.isNumber(minY)) {
71662             axis = Ext.create('Ext.chart.axis.Axis', {
71663                 chart: chart,
71664                 fields: [].concat(me.yField)
71665             });
71666             out = axis.calcEnds();
71667             minY = out.from;
71668             maxY = out.to;
71669         }
71670
71671         if (!Ext.isNumber(minY)) {
71672             minY = 0;
71673         }
71674         if (!Ext.isNumber(maxY)) {
71675             maxY = 0;
71676         }
71677         scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (maxY - minY);
71678         groupBarWidth = barWidth / ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter);
71679         zero = (column) ? bbox.y + bbox.height - yPadding : bbox.x + xPadding;
71680
71681         if (stacked) {
71682             total = [[], []];
71683             store.each(function(record, i) {
71684                 total[0][i] = total[0][i] || 0;
71685                 total[1][i] = total[1][i] || 0;
71686                 for (j = 0; j < barsLen; j++) {
71687                     if (me.__excludes && me.__excludes[j]) {
71688                         continue;
71689                     }
71690                     rec = record.get(bars[j]);
71691                     total[+(rec > 0)][i] += mabs(rec);
71692                 }
71693             });
71694             total[+(maxY > 0)].push(mabs(maxY));
71695             total[+(minY > 0)].push(mabs(minY));
71696             minus = mmax.apply(math, total[0]);
71697             plus = mmax.apply(math, total[1]);
71698             scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (plus + minus);
71699             zero = zero + minus * scale * (column ? -1 : 1);
71700         }
71701         else if (minY / maxY < 0) {
71702             zero = zero - minY * scale * (column ? -1 : 1);
71703         }
71704         return {
71705             bars: bars,
71706             bbox: bbox,
71707             barsLen: barsLen,
71708             groupBarsLen: groupBarsLen,
71709             barWidth: barWidth,
71710             groupBarWidth: groupBarWidth,
71711             scale: scale,
71712             zero: zero,
71713             xPadding: xPadding,
71714             yPadding: yPadding,
71715             signed: minY / maxY < 0,
71716             minY: minY,
71717             maxY: maxY
71718         };
71719     },
71720
71721     // @private Build an array of paths for the chart
71722     getPaths: function() {
71723         var me = this,
71724             chart = me.chart,
71725             store = chart.getChartStore(),
71726             bounds = me.bounds = me.getBounds(),
71727             items = me.items = [],
71728             gutter = me.gutter / 100,
71729             groupGutter = me.groupGutter / 100,
71730             animate = chart.animate,
71731             column = me.column,
71732             group = me.group,
71733             enableShadows = chart.shadow,
71734             shadowGroups = me.shadowGroups,
71735             shadowAttributes = me.shadowAttributes,
71736             shadowGroupsLn = shadowGroups.length,
71737             bbox = bounds.bbox,
71738             xPadding = me.xPadding,
71739             yPadding = me.yPadding,
71740             stacked = me.stacked,
71741             barsLen = bounds.barsLen,
71742             colors = me.colorArrayStyle,
71743             colorLength = colors && colors.length || 0,
71744             math = Math,
71745             mmax = math.max,
71746             mmin = math.min,
71747             mabs = math.abs,
71748             j, yValue, height, totalDim, totalNegDim, bottom, top, hasShadow, barAttr, attrs, counter,
71749             shadowIndex, shadow, sprite, offset, floorY;
71750
71751         store.each(function(record, i, total) {
71752             bottom = bounds.zero;
71753             top = bounds.zero;
71754             totalDim = 0;
71755             totalNegDim = 0;
71756             hasShadow = false;
71757             for (j = 0, counter = 0; j < barsLen; j++) {
71758                 // Excluded series
71759                 if (me.__excludes && me.__excludes[j]) {
71760                     continue;
71761                 }
71762                 yValue = record.get(bounds.bars[j]);
71763                 height = Math.round((yValue - mmax(bounds.minY, 0)) * bounds.scale);
71764                 barAttr = {
71765                     fill: colors[(barsLen > 1 ? j : 0) % colorLength]
71766                 };
71767                 if (column) {
71768                     Ext.apply(barAttr, {
71769                         height: height,
71770                         width: mmax(bounds.groupBarWidth, 0),
71771                         x: (bbox.x + xPadding + i * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked),
71772                         y: bottom - height
71773                     });
71774                 }
71775                 else {
71776                     // draw in reverse order
71777                     offset = (total - 1) - i;
71778                     Ext.apply(barAttr, {
71779                         height: mmax(bounds.groupBarWidth, 0),
71780                         width: height + (bottom == bounds.zero),
71781                         x: bottom + (bottom != bounds.zero),
71782                         y: (bbox.y + yPadding + offset * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked + 1)
71783                     });
71784                 }
71785                 if (height < 0) {
71786                     if (column) {
71787                         barAttr.y = top;
71788                         barAttr.height = mabs(height);
71789                     } else {
71790                         barAttr.x = top + height;
71791                         barAttr.width = mabs(height);
71792                     }
71793                 }
71794                 if (stacked) {
71795                     if (height < 0) {
71796                         top += height * (column ? -1 : 1);
71797                     } else {
71798                         bottom += height * (column ? -1 : 1);
71799                     }
71800                     totalDim += mabs(height);
71801                     if (height < 0) {
71802                         totalNegDim += mabs(height);
71803                     }
71804                 }
71805                 barAttr.x = Math.floor(barAttr.x) + 1;
71806                 floorY = Math.floor(barAttr.y);
71807                 if (!Ext.isIE9 && barAttr.y > floorY) {
71808                     floorY--;
71809                 }
71810                 barAttr.y = floorY;
71811                 barAttr.width = Math.floor(barAttr.width);
71812                 barAttr.height = Math.floor(barAttr.height);
71813                 items.push({
71814                     series: me,
71815                     storeItem: record,
71816                     value: [record.get(me.xField), yValue],
71817                     attr: barAttr,
71818                     point: column ? [barAttr.x + barAttr.width / 2, yValue >= 0 ? barAttr.y : barAttr.y + barAttr.height] :
71819                                     [yValue >= 0 ? barAttr.x + barAttr.width : barAttr.x, barAttr.y + barAttr.height / 2]
71820                 });
71821                 // When resizing, reset before animating
71822                 if (animate && chart.resizing) {
71823                     attrs = column ? {
71824                         x: barAttr.x,
71825                         y: bounds.zero,
71826                         width: barAttr.width,
71827                         height: 0
71828                     } : {
71829                         x: bounds.zero,
71830                         y: barAttr.y,
71831                         width: 0,
71832                         height: barAttr.height
71833                     };
71834                     if (enableShadows && (stacked && !hasShadow || !stacked)) {
71835                         hasShadow = true;
71836                         //update shadows
71837                         for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71838                             shadow = shadowGroups[shadowIndex].getAt(stacked ? i : (i * barsLen + j));
71839                             if (shadow) {
71840                                 shadow.setAttributes(attrs, true);
71841                             }
71842                         }
71843                     }
71844                     //update sprite position and width/height
71845                     sprite = group.getAt(i * barsLen + j);
71846                     if (sprite) {
71847                         sprite.setAttributes(attrs, true);
71848                     }
71849                 }
71850                 counter++;
71851             }
71852             if (stacked && items.length) {
71853                 items[i * counter].totalDim = totalDim;
71854                 items[i * counter].totalNegDim = totalNegDim;
71855             }
71856         }, me);
71857     },
71858
71859     // @private render/setAttributes on the shadows
71860     renderShadows: function(i, barAttr, baseAttrs, bounds) {
71861         var me = this,
71862             chart = me.chart,
71863             surface = chart.surface,
71864             animate = chart.animate,
71865             stacked = me.stacked,
71866             shadowGroups = me.shadowGroups,
71867             shadowAttributes = me.shadowAttributes,
71868             shadowGroupsLn = shadowGroups.length,
71869             store = chart.getChartStore(),
71870             column = me.column,
71871             items = me.items,
71872             shadows = [],
71873             zero = bounds.zero,
71874             shadowIndex, shadowBarAttr, shadow, totalDim, totalNegDim, j, rendererAttributes;
71875
71876         if ((stacked && (i % bounds.groupBarsLen === 0)) || !stacked) {
71877             j = i / bounds.groupBarsLen;
71878             //create shadows
71879             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71880                 shadowBarAttr = Ext.apply({}, shadowAttributes[shadowIndex]);
71881                 shadow = shadowGroups[shadowIndex].getAt(stacked ? j : i);
71882                 Ext.copyTo(shadowBarAttr, barAttr, 'x,y,width,height');
71883                 if (!shadow) {
71884                     shadow = surface.add(Ext.apply({
71885                         type: 'rect',
71886                         group: shadowGroups[shadowIndex]
71887                     }, Ext.apply({}, baseAttrs, shadowBarAttr)));
71888                 }
71889                 if (stacked) {
71890                     totalDim = items[i].totalDim;
71891                     totalNegDim = items[i].totalNegDim;
71892                     if (column) {
71893                         shadowBarAttr.y = zero - totalNegDim;
71894                         shadowBarAttr.height = totalDim;
71895                     }
71896                     else {
71897                         shadowBarAttr.x = zero - totalNegDim;
71898                         shadowBarAttr.width = totalDim;
71899                     }
71900                 }
71901                 if (animate) {
71902                     if (!stacked) {
71903                         rendererAttributes = me.renderer(shadow, store.getAt(j), shadowBarAttr, i, store);
71904                         me.onAnimate(shadow, { to: rendererAttributes });
71905                     }
71906                     else {
71907                         rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: true }), i, store);
71908                         shadow.setAttributes(rendererAttributes, true);
71909                     }
71910                 }
71911                 else {
71912                     rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: false }), i, store);
71913                     shadow.setAttributes(rendererAttributes, true);
71914                 }
71915                 shadows.push(shadow);
71916             }
71917         }
71918         return shadows;
71919     },
71920
71921     /**
71922      * Draws the series for the current chart.
71923      */
71924     drawSeries: function() {
71925         var me = this,
71926             chart = me.chart,
71927             store = chart.getChartStore(),
71928             surface = chart.surface,
71929             animate = chart.animate,
71930             stacked = me.stacked,
71931             column = me.column,
71932             enableShadows = chart.shadow,
71933             shadowGroups = me.shadowGroups,
71934             shadowGroupsLn = shadowGroups.length,
71935             group = me.group,
71936             seriesStyle = me.seriesStyle,
71937             items, ln, i, j, baseAttrs, sprite, rendererAttributes, shadowIndex, shadowGroup,
71938             bounds, endSeriesStyle, barAttr, attrs, anim;
71939
71940         if (!store || !store.getCount()) {
71941             return;
71942         }
71943
71944         //fill colors are taken from the colors array.
71945         delete seriesStyle.fill;
71946         endSeriesStyle = Ext.apply(seriesStyle, this.style);
71947         me.unHighlightItem();
71948         me.cleanHighlights();
71949
71950         me.getPaths();
71951         bounds = me.bounds;
71952         items = me.items;
71953
71954         baseAttrs = column ? {
71955             y: bounds.zero,
71956             height: 0
71957         } : {
71958             x: bounds.zero,
71959             width: 0
71960         };
71961         ln = items.length;
71962         // Create new or reuse sprites and animate/display
71963         for (i = 0; i < ln; i++) {
71964             sprite = group.getAt(i);
71965             barAttr = items[i].attr;
71966
71967             if (enableShadows) {
71968                 items[i].shadows = me.renderShadows(i, barAttr, baseAttrs, bounds);
71969             }
71970
71971             // Create a new sprite if needed (no height)
71972             if (!sprite) {
71973                 attrs = Ext.apply({}, baseAttrs, barAttr);
71974                 attrs = Ext.apply(attrs, endSeriesStyle || {});
71975                 sprite = surface.add(Ext.apply({}, {
71976                     type: 'rect',
71977                     group: group
71978                 }, attrs));
71979             }
71980             if (animate) {
71981                 rendererAttributes = me.renderer(sprite, store.getAt(i), barAttr, i, store);
71982                 sprite._to = rendererAttributes;
71983                 anim = me.onAnimate(sprite, { to: Ext.apply(rendererAttributes, endSeriesStyle) });
71984                 if (enableShadows && stacked && (i % bounds.barsLen === 0)) {
71985                     j = i / bounds.barsLen;
71986                     for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71987                         anim.on('afteranimate', function() {
71988                             this.show(true);
71989                         }, shadowGroups[shadowIndex].getAt(j));
71990                     }
71991                 }
71992             }
71993             else {
71994                 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(barAttr, { hidden: false }), i, store);
71995                 sprite.setAttributes(Ext.apply(rendererAttributes, endSeriesStyle), true);
71996             }
71997             items[i].sprite = sprite;
71998         }
71999
72000         // Hide unused sprites
72001         ln = group.getCount();
72002         for (j = i; j < ln; j++) {
72003             group.getAt(j).hide(true);
72004         }
72005         // Hide unused shadows
72006         if (enableShadows) {
72007             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
72008                 shadowGroup = shadowGroups[shadowIndex];
72009                 ln = shadowGroup.getCount();
72010                 for (j = i; j < ln; j++) {
72011                     shadowGroup.getAt(j).hide(true);
72012                 }
72013             }
72014         }
72015         me.renderLabels();
72016     },
72017
72018     // @private handled when creating a label.
72019     onCreateLabel: function(storeItem, item, i, display) {
72020         var me = this,
72021             surface = me.chart.surface,
72022             group = me.labelsGroup,
72023             config = me.label,
72024             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle || {}),
72025             sprite;
72026         return surface.add(Ext.apply({
72027             type: 'text',
72028             group: group
72029         }, endLabelStyle || {}));
72030     },
72031
72032     // @private callback used when placing a label.
72033     onPlaceLabel: function(label, storeItem, item, i, display, animate, j, index) {
72034         // Determine the label's final position. Starts with the configured preferred value but
72035         // may get flipped from inside to outside or vice-versa depending on space.
72036         var me = this,
72037             opt = me.bounds,
72038             groupBarWidth = opt.groupBarWidth,
72039             column = me.column,
72040             chart = me.chart,
72041             chartBBox = chart.chartBBox,
72042             resizing = chart.resizing,
72043             xValue = item.value[0],
72044             yValue = item.value[1],
72045             attr = item.attr,
72046             config = me.label,
72047             rotate = config.orientation == 'vertical',
72048             field = [].concat(config.field),
72049             format = config.renderer,
72050             text = format(storeItem.get(field[index])),
72051             size = me.getLabelSize(text),
72052             width = size.width,
72053             height = size.height,
72054             zero = opt.zero,
72055             outside = 'outside',
72056             insideStart = 'insideStart',
72057             insideEnd = 'insideEnd',
72058             offsetX = 10,
72059             offsetY = 6,
72060             signed = opt.signed,
72061             x, y, finalAttr;
72062
72063         label.setAttributes({
72064             text: text
72065         });
72066
72067         label.isOutside = false;
72068         if (column) {
72069             if (display == outside) {
72070                 if (height + offsetY + attr.height > (yValue >= 0 ? zero - chartBBox.y : chartBBox.y + chartBBox.height - zero)) {
72071                     display = insideEnd;
72072                 }
72073             } else {
72074                 if (height + offsetY > attr.height) {
72075                     display = outside;
72076                     label.isOutside = true;
72077                 }
72078             }
72079             x = attr.x + groupBarWidth / 2;
72080             y = display == insideStart ?
72081                     (zero + ((height / 2 + 3) * (yValue >= 0 ? -1 : 1))) :
72082                     (yValue >= 0 ? (attr.y + ((height / 2 + 3) * (display == outside ? -1 : 1))) :
72083                                    (attr.y + attr.height + ((height / 2 + 3) * (display === outside ? 1 : -1))));
72084         }
72085         else {
72086             if (display == outside) {
72087                 if (width + offsetX + attr.width > (yValue >= 0 ? chartBBox.x + chartBBox.width - zero : zero - chartBBox.x)) {
72088                     display = insideEnd;
72089                 }
72090             }
72091             else {
72092                 if (width + offsetX > attr.width) {
72093                     display = outside;
72094                     label.isOutside = true;
72095                 }
72096             }
72097             x = display == insideStart ?
72098                 (zero + ((width / 2 + 5) * (yValue >= 0 ? 1 : -1))) :
72099                 (yValue >= 0 ? (attr.x + attr.width + ((width / 2 + 5) * (display === outside ? 1 : -1))) :
72100                 (attr.x + ((width / 2 + 5) * (display === outside ? -1 : 1))));
72101             y = attr.y + groupBarWidth / 2;
72102         }
72103         //set position
72104         finalAttr = {
72105             x: x,
72106             y: y
72107         };
72108         //rotate
72109         if (rotate) {
72110             finalAttr.rotate = {
72111                 x: x,
72112                 y: y,
72113                 degrees: 270
72114             };
72115         }
72116         //check for resizing
72117         if (animate && resizing) {
72118             if (column) {
72119                 x = attr.x + attr.width / 2;
72120                 y = zero;
72121             } else {
72122                 x = zero;
72123                 y = attr.y + attr.height / 2;
72124             }
72125             label.setAttributes({
72126                 x: x,
72127                 y: y
72128             }, true);
72129             if (rotate) {
72130                 label.setAttributes({
72131                     rotate: {
72132                         x: x,
72133                         y: y,
72134                         degrees: 270
72135                     }
72136                 }, true);
72137             }
72138         }
72139         //handle animation
72140         if (animate) {
72141             me.onAnimate(label, { to: finalAttr });
72142         }
72143         else {
72144             label.setAttributes(Ext.apply(finalAttr, {
72145                 hidden: false
72146             }), true);
72147         }
72148     },
72149
72150     /* @private
72151      * Gets the dimensions of a given bar label. Uses a single hidden sprite to avoid
72152      * changing visible sprites.
72153      * @param value
72154      */
72155     getLabelSize: function(value) {
72156         var tester = this.testerLabel,
72157             config = this.label,
72158             endLabelStyle = Ext.apply({}, config, this.seriesLabelStyle || {}),
72159             rotated = config.orientation === 'vertical',
72160             bbox, w, h,
72161             undef;
72162         if (!tester) {
72163             tester = this.testerLabel = this.chart.surface.add(Ext.apply({
72164                 type: 'text',
72165                 opacity: 0
72166             }, endLabelStyle));
72167         }
72168         tester.setAttributes({
72169             text: value
72170         }, true);
72171
72172         // Flip the width/height if rotated, as getBBox returns the pre-rotated dimensions
72173         bbox = tester.getBBox();
72174         w = bbox.width;
72175         h = bbox.height;
72176         return {
72177             width: rotated ? h : w,
72178             height: rotated ? w : h
72179         };
72180     },
72181
72182     // @private used to animate label, markers and other sprites.
72183     onAnimate: function(sprite, attr) {
72184         sprite.show();
72185         return this.callParent(arguments);
72186     },
72187
72188     isItemInPoint: function(x, y, item) {
72189         var bbox = item.sprite.getBBox();
72190         return bbox.x <= x && bbox.y <= y
72191             && (bbox.x + bbox.width) >= x
72192             && (bbox.y + bbox.height) >= y;
72193     },
72194
72195     // @private hide all markers
72196     hideAll: function() {
72197         var axes = this.chart.axes;
72198         if (!isNaN(this._index)) {
72199             if (!this.__excludes) {
72200                 this.__excludes = [];
72201             }
72202             this.__excludes[this._index] = true;
72203             this.drawSeries();
72204             axes.each(function(axis) {
72205                 axis.drawAxis();
72206             });
72207         }
72208     },
72209
72210     // @private show all markers
72211     showAll: function() {
72212         var axes = this.chart.axes;
72213         if (!isNaN(this._index)) {
72214             if (!this.__excludes) {
72215                 this.__excludes = [];
72216             }
72217             this.__excludes[this._index] = false;
72218             this.drawSeries();
72219             axes.each(function(axis) {
72220                 axis.drawAxis();
72221             });
72222         }
72223     },
72224
72225     /**
72226      * Returns a string with the color to be used for the series legend item.
72227      * @param index
72228      */
72229     getLegendColor: function(index) {
72230         var me = this,
72231             colorLength = me.colorArrayStyle.length;
72232
72233         if (me.style && me.style.fill) {
72234             return me.style.fill;
72235         } else {
72236             return me.colorArrayStyle[index % colorLength];
72237         }
72238     },
72239
72240     highlightItem: function(item) {
72241         this.callParent(arguments);
72242         this.renderLabels();
72243     },
72244
72245     unHighlightItem: function() {
72246         this.callParent(arguments);
72247         this.renderLabels();
72248     },
72249
72250     cleanHighlights: function() {
72251         this.callParent(arguments);
72252         this.renderLabels();
72253     }
72254 });
72255 /**
72256  * @class Ext.chart.series.Column
72257  * @extends Ext.chart.series.Bar
72258  *
72259  * Creates a Column Chart. Much of the methods are inherited from Bar. A Column Chart is a useful
72260  * visualization technique to display quantitative information for different categories that can
72261  * show some progression (or regression) in the data set. As with all other series, the Column Series
72262  * must be appended in the *series* Chart array configuration. See the Chart documentation for more
72263  * information. A typical configuration object for the column series could be:
72264  *
72265  *     @example
72266  *     var store = Ext.create('Ext.data.JsonStore', {
72267  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
72268  *         data: [
72269  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
72270  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
72271  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
72272  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
72273  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
72274  *         ]
72275  *     });
72276  *
72277  *     Ext.create('Ext.chart.Chart', {
72278  *         renderTo: Ext.getBody(),
72279  *         width: 500,
72280  *         height: 300,
72281  *         animate: true,
72282  *         store: store,
72283  *         axes: [
72284  *             {
72285  *                 type: 'Numeric',
72286  *                 position: 'left',
72287  *                 fields: ['data1'],
72288  *                 label: {
72289  *                     renderer: Ext.util.Format.numberRenderer('0,0')
72290  *                 },
72291  *                 title: 'Sample Values',
72292  *                 grid: true,
72293  *                 minimum: 0
72294  *             },
72295  *             {
72296  *                 type: 'Category',
72297  *                 position: 'bottom',
72298  *                 fields: ['name'],
72299  *                 title: 'Sample Metrics'
72300  *             }
72301  *         ],
72302  *         series: [
72303  *             {
72304  *                 type: 'column',
72305  *                 axis: 'left',
72306  *                 highlight: true,
72307  *                 tips: {
72308  *                   trackMouse: true,
72309  *                   width: 140,
72310  *                   height: 28,
72311  *                   renderer: function(storeItem, item) {
72312  *                     this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' $');
72313  *                   }
72314  *                 },
72315  *                 label: {
72316  *                   display: 'insideEnd',
72317  *                   'text-anchor': 'middle',
72318  *                     field: 'data1',
72319  *                     renderer: Ext.util.Format.numberRenderer('0'),
72320  *                     orientation: 'vertical',
72321  *                     color: '#333'
72322  *                 },
72323  *                 xField: 'name',
72324  *                 yField: 'data1'
72325  *             }
72326  *         ]
72327  *     });
72328  *
72329  * In this configuration we set `column` as the series type, bind the values of the bars to the bottom axis,
72330  * set `highlight` to true so that bars are smoothly highlighted when hovered and bind the `xField` or category
72331  * field to the data store `name` property and the `yField` as the data1 property of a store element.
72332  */
72333 Ext.define('Ext.chart.series.Column', {
72334
72335     /* Begin Definitions */
72336
72337     alternateClassName: ['Ext.chart.ColumnSeries', 'Ext.chart.ColumnChart', 'Ext.chart.StackedColumnChart'],
72338
72339     extend: 'Ext.chart.series.Bar',
72340
72341     /* End Definitions */
72342
72343     type: 'column',
72344     alias: 'series.column',
72345
72346     column: true,
72347
72348     /**
72349      * @cfg {Number} xPadding
72350      * Padding between the left/right axes and the bars
72351      */
72352     xPadding: 10,
72353
72354     /**
72355      * @cfg {Number} yPadding
72356      * Padding between the top/bottom axes and the bars
72357      */
72358     yPadding: 0
72359 });
72360 /**
72361  * @class Ext.chart.series.Gauge
72362  * @extends Ext.chart.series.Series
72363  * 
72364  * Creates a Gauge Chart. Gauge Charts are used to show progress in a certain variable. There are two ways of using the Gauge chart.
72365  * One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instantiating the
72366  * visualization and using the `setValue` method to adjust the value you want.
72367  *
72368  * A chart/series configuration for the Gauge visualization could look like this:
72369  * 
72370  *     {
72371  *         xtype: 'chart',
72372  *         store: store,
72373  *         axes: [{
72374  *             type: 'gauge',
72375  *             position: 'gauge',
72376  *             minimum: 0,
72377  *             maximum: 100,
72378  *             steps: 10,
72379  *             margin: -10
72380  *         }],
72381  *         series: [{
72382  *             type: 'gauge',
72383  *             field: 'data1',
72384  *             donut: false,
72385  *             colorSet: ['#F49D10', '#ddd']
72386  *         }]
72387  *     }
72388  * 
72389  * In this configuration we create a special Gauge axis to be used with the gauge visualization (describing half-circle markers), and also we're
72390  * setting a maximum, minimum and steps configuration options into the axis. The Gauge series configuration contains the store field to be bound to
72391  * the visual display and the color set to be used with the visualization.
72392  * 
72393  * @xtype gauge
72394  */
72395 Ext.define('Ext.chart.series.Gauge', {
72396
72397     /* Begin Definitions */
72398
72399     extend: 'Ext.chart.series.Series',
72400
72401     /* End Definitions */
72402
72403     type: "gauge",
72404     alias: 'series.gauge',
72405
72406     rad: Math.PI / 180,
72407
72408     /**
72409      * @cfg {Number} highlightDuration
72410      * The duration for the pie slice highlight effect.
72411      */
72412     highlightDuration: 150,
72413
72414     /**
72415      * @cfg {String} angleField (required)
72416      * The store record field name to be used for the pie angles.
72417      * The values bound to this field name must be positive real numbers.
72418      */
72419     angleField: false,
72420
72421     /**
72422      * @cfg {Boolean} needle
72423      * Use the Gauge Series as an area series or add a needle to it. Default's false.
72424      */
72425     needle: false,
72426     
72427     /**
72428      * @cfg {Boolean/Number} donut
72429      * Use the entire disk or just a fraction of it for the gauge. Default's false.
72430      */
72431     donut: false,
72432
72433     /**
72434      * @cfg {Boolean} showInLegend
72435      * Whether to add the pie chart elements as legend items. Default's false.
72436      */
72437     showInLegend: false,
72438
72439     /**
72440      * @cfg {Object} style
72441      * An object containing styles for overriding series styles from Theming.
72442      */
72443     style: {},
72444     
72445     constructor: function(config) {
72446         this.callParent(arguments);
72447         var me = this,
72448             chart = me.chart,
72449             surface = chart.surface,
72450             store = chart.store,
72451             shadow = chart.shadow, i, l, cfg;
72452         Ext.apply(me, config, {
72453             shadowAttributes: [{
72454                 "stroke-width": 6,
72455                 "stroke-opacity": 1,
72456                 stroke: 'rgb(200, 200, 200)',
72457                 translate: {
72458                     x: 1.2,
72459                     y: 2
72460                 }
72461             },
72462             {
72463                 "stroke-width": 4,
72464                 "stroke-opacity": 1,
72465                 stroke: 'rgb(150, 150, 150)',
72466                 translate: {
72467                     x: 0.9,
72468                     y: 1.5
72469                 }
72470             },
72471             {
72472                 "stroke-width": 2,
72473                 "stroke-opacity": 1,
72474                 stroke: 'rgb(100, 100, 100)',
72475                 translate: {
72476                     x: 0.6,
72477                     y: 1
72478                 }
72479             }]
72480         });
72481         me.group = surface.getGroup(me.seriesId);
72482         if (shadow) {
72483             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
72484                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
72485             }
72486         }
72487         surface.customAttributes.segment = function(opt) {
72488             return me.getSegment(opt);
72489         };
72490     },
72491     
72492     //@private updates some onbefore render parameters.
72493     initialize: function() {
72494         var me = this,
72495             store = me.chart.getChartStore();
72496         //Add yFields to be used in Legend.js
72497         me.yField = [];
72498         if (me.label.field) {
72499             store.each(function(rec) {
72500                 me.yField.push(rec.get(me.label.field));
72501             });
72502         }
72503     },
72504
72505     // @private returns an object with properties for a Slice
72506     getSegment: function(opt) {
72507         var me = this,
72508             rad = me.rad,
72509             cos = Math.cos,
72510             sin = Math.sin,
72511             abs = Math.abs,
72512             x = me.centerX,
72513             y = me.centerY,
72514             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
72515             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
72516             delta = 1e-2,
72517             r = opt.endRho - opt.startRho,
72518             startAngle = opt.startAngle,
72519             endAngle = opt.endAngle,
72520             midAngle = (startAngle + endAngle) / 2 * rad,
72521             margin = opt.margin || 0,
72522             flag = abs(endAngle - startAngle) > 180,
72523             a1 = Math.min(startAngle, endAngle) * rad,
72524             a2 = Math.max(startAngle, endAngle) * rad,
72525             singleSlice = false;
72526
72527         x += margin * cos(midAngle);
72528         y += margin * sin(midAngle);
72529
72530         x1 = x + opt.startRho * cos(a1);
72531         y1 = y + opt.startRho * sin(a1);
72532
72533         x2 = x + opt.endRho * cos(a1);
72534         y2 = y + opt.endRho * sin(a1);
72535
72536         x3 = x + opt.startRho * cos(a2);
72537         y3 = y + opt.startRho * sin(a2);
72538
72539         x4 = x + opt.endRho * cos(a2);
72540         y4 = y + opt.endRho * sin(a2);
72541
72542         if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
72543             singleSlice = true;
72544         }
72545         //Solves mysterious clipping bug with IE
72546         if (singleSlice) {
72547             return {
72548                 path: [
72549                 ["M", x1, y1],
72550                 ["L", x2, y2],
72551                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
72552                 ["Z"]]
72553             };
72554         } else {
72555             return {
72556                 path: [
72557                 ["M", x1, y1],
72558                 ["L", x2, y2],
72559                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
72560                 ["L", x3, y3],
72561                 ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
72562                 ["Z"]]
72563             };
72564         }
72565     },
72566
72567     // @private utility function to calculate the middle point of a pie slice.
72568     calcMiddle: function(item) {
72569         var me = this,
72570             rad = me.rad,
72571             slice = item.slice,
72572             x = me.centerX,
72573             y = me.centerY,
72574             startAngle = slice.startAngle,
72575             endAngle = slice.endAngle,
72576             radius = Math.max(('rho' in slice) ? slice.rho: me.radius, me.label.minMargin),
72577             donut = +me.donut,
72578             a1 = Math.min(startAngle, endAngle) * rad,
72579             a2 = Math.max(startAngle, endAngle) * rad,
72580             midAngle = -(a1 + (a2 - a1) / 2),
72581             xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
72582             ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
72583
72584         item.middle = {
72585             x: xm,
72586             y: ym
72587         };
72588     },
72589
72590     /**
72591      * Draws the series for the current chart.
72592      */
72593     drawSeries: function() {
72594         var me = this,
72595             chart = me.chart,
72596             store = chart.getChartStore(),
72597             group = me.group,
72598             animate = me.chart.animate,
72599             axis = me.chart.axes.get(0),
72600             minimum = axis && axis.minimum || me.minimum || 0,
72601             maximum = axis && axis.maximum || me.maximum || 0,
72602             field = me.angleField || me.field || me.xField,
72603             surface = chart.surface,
72604             chartBBox = chart.chartBBox,
72605             rad = me.rad,
72606             donut = +me.donut,
72607             values = {},
72608             items = [],
72609             seriesStyle = me.seriesStyle,
72610             seriesLabelStyle = me.seriesLabelStyle,
72611             colorArrayStyle = me.colorArrayStyle,
72612             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
72613             gutterX = chart.maxGutter[0],
72614             gutterY = chart.maxGutter[1],
72615             cos = Math.cos,
72616             sin = Math.sin,
72617             rendererAttributes, centerX, centerY, slice, slices, sprite, value,
72618             item, ln, record, i, j, startAngle, endAngle, middleAngle, sliceLength, path,
72619             p, spriteOptions, bbox, splitAngle, sliceA, sliceB;
72620         
72621         Ext.apply(seriesStyle, me.style || {});
72622
72623         me.setBBox();
72624         bbox = me.bbox;
72625
72626         //override theme colors
72627         if (me.colorSet) {
72628             colorArrayStyle = me.colorSet;
72629             colorArrayLength = colorArrayStyle.length;
72630         }
72631         
72632         //if not store or store is empty then there's nothing to draw
72633         if (!store || !store.getCount()) {
72634             return;
72635         }
72636         
72637         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
72638         centerY = me.centerY = chartBBox.y + chartBBox.height;
72639         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
72640         me.slices = slices = [];
72641         me.items = items = [];
72642         
72643         if (!me.value) {
72644             record = store.getAt(0);
72645             me.value = record.get(field);
72646         }
72647         
72648         value = me.value;
72649         if (me.needle) {
72650             sliceA = {
72651                 series: me,
72652                 value: value,
72653                 startAngle: -180,
72654                 endAngle: 0,
72655                 rho: me.radius
72656             };
72657             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
72658             slices.push(sliceA);
72659         } else {
72660             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
72661             sliceA = {
72662                 series: me,
72663                 value: value,
72664                 startAngle: -180,
72665                 endAngle: splitAngle,
72666                 rho: me.radius
72667             };
72668             sliceB = {
72669                 series: me,
72670                 value: me.maximum - value,
72671                 startAngle: splitAngle,
72672                 endAngle: 0,
72673                 rho: me.radius
72674             };
72675             slices.push(sliceA, sliceB);
72676         }
72677         
72678         //do pie slices after.
72679         for (i = 0, ln = slices.length; i < ln; i++) {
72680             slice = slices[i];
72681             sprite = group.getAt(i);
72682             //set pie slice properties
72683             rendererAttributes = Ext.apply({
72684                 segment: {
72685                     startAngle: slice.startAngle,
72686                     endAngle: slice.endAngle,
72687                     margin: 0,
72688                     rho: slice.rho,
72689                     startRho: slice.rho * +donut / 100,
72690                     endRho: slice.rho
72691                 } 
72692             }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
72693
72694             item = Ext.apply({},
72695             rendererAttributes.segment, {
72696                 slice: slice,
72697                 series: me,
72698                 storeItem: record,
72699                 index: i
72700             });
72701             items[i] = item;
72702             // Create a new sprite if needed (no height)
72703             if (!sprite) {
72704                 spriteOptions = Ext.apply({
72705                     type: "path",
72706                     group: group
72707                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
72708                 sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
72709             }
72710             slice.sprite = slice.sprite || [];
72711             item.sprite = sprite;
72712             slice.sprite.push(sprite);
72713             if (animate) {
72714                 rendererAttributes = me.renderer(sprite, record, rendererAttributes, i, store);
72715                 sprite._to = rendererAttributes;
72716                 me.onAnimate(sprite, {
72717                     to: rendererAttributes
72718                 });
72719             } else {
72720                 rendererAttributes = me.renderer(sprite, record, Ext.apply(rendererAttributes, {
72721                     hidden: false
72722                 }), i, store);
72723                 sprite.setAttributes(rendererAttributes, true);
72724             }
72725         }
72726         
72727         if (me.needle) {
72728             splitAngle = splitAngle * Math.PI / 180;
72729             
72730             if (!me.needleSprite) {
72731                 me.needleSprite = me.chart.surface.add({
72732                     type: 'path',
72733                     path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72734                                 centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72735                            'L', centerX + me.radius * cos(splitAngle),
72736                                 centerY + -Math.abs(me.radius * sin(splitAngle))],
72737                     'stroke-width': 4,
72738                     'stroke': '#222'
72739                 });
72740             } else {
72741                 if (animate) {
72742                     me.onAnimate(me.needleSprite, {
72743                         to: {
72744                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72745                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72746                                'L', centerX + me.radius * cos(splitAngle),
72747                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
72748                         }
72749                     });
72750                 } else {
72751                     me.needleSprite.setAttributes({
72752                         type: 'path',
72753                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72754                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72755                                'L', centerX + me.radius * cos(splitAngle),
72756                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
72757                     });
72758                 }
72759             }
72760             me.needleSprite.setAttributes({
72761                 hidden: false    
72762             }, true);
72763         }
72764         
72765         delete me.value;
72766     },
72767     
72768     /**
72769      * Sets the Gauge chart to the current specified value.
72770     */
72771     setValue: function (value) {
72772         this.value = value;
72773         this.drawSeries();
72774     },
72775
72776     // @private callback for when creating a label sprite.
72777     onCreateLabel: function(storeItem, item, i, display) {},
72778
72779     // @private callback for when placing a label sprite.
72780     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {},
72781
72782     // @private callback for when placing a callout.
72783     onPlaceCallout: function() {},
72784
72785     // @private handles sprite animation for the series.
72786     onAnimate: function(sprite, attr) {
72787         sprite.show();
72788         return this.callParent(arguments);
72789     },
72790
72791     isItemInPoint: function(x, y, item, i) {
72792         return false;
72793     },
72794     
72795     // @private shows all elements in the series.
72796     showAll: function() {
72797         if (!isNaN(this._index)) {
72798             this.__excludes[this._index] = false;
72799             this.drawSeries();
72800         }
72801     },
72802     
72803     /**
72804      * Returns the color of the series (to be displayed as color for the series legend item).
72805      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
72806      */
72807     getLegendColor: function(index) {
72808         var me = this;
72809         return me.colorArrayStyle[index % me.colorArrayStyle.length];
72810     }
72811 });
72812
72813
72814 /**
72815  * @class Ext.chart.series.Line
72816  * @extends Ext.chart.series.Cartesian
72817  *
72818  * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
72819  * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
72820  * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
72821  * documentation for more information. A typical configuration object for the line series could be:
72822  *
72823  *     @example
72824  *     var store = Ext.create('Ext.data.JsonStore', {
72825  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
72826  *         data: [
72827  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
72828  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
72829  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
72830  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
72831  *             { 'name': 'metric five',  'data1': 4,  'data2': 4,  'data3': 36, 'data4': 13, 'data5': 33 }
72832  *         ]
72833  *     });
72834  *
72835  *     Ext.create('Ext.chart.Chart', {
72836  *         renderTo: Ext.getBody(),
72837  *         width: 500,
72838  *         height: 300,
72839  *         animate: true,
72840  *         store: store,
72841  *         axes: [
72842  *             {
72843  *                 type: 'Numeric',
72844  *                 position: 'left',
72845  *                 fields: ['data1', 'data2'],
72846  *                 label: {
72847  *                     renderer: Ext.util.Format.numberRenderer('0,0')
72848  *                 },
72849  *                 title: 'Sample Values',
72850  *                 grid: true,
72851  *                 minimum: 0
72852  *             },
72853  *             {
72854  *                 type: 'Category',
72855  *                 position: 'bottom',
72856  *                 fields: ['name'],
72857  *                 title: 'Sample Metrics'
72858  *             }
72859  *         ],
72860  *         series: [
72861  *             {
72862  *                 type: 'line',
72863  *                 highlight: {
72864  *                     size: 7,
72865  *                     radius: 7
72866  *                 },
72867  *                 axis: 'left',
72868  *                 xField: 'name',
72869  *                 yField: 'data1',
72870  *                 markerConfig: {
72871  *                     type: 'cross',
72872  *                     size: 4,
72873  *                     radius: 4,
72874  *                     'stroke-width': 0
72875  *                 }
72876  *             },
72877  *             {
72878  *                 type: 'line',
72879  *                 highlight: {
72880  *                     size: 7,
72881  *                     radius: 7
72882  *                 },
72883  *                 axis: 'left',
72884  *                 fill: true,
72885  *                 xField: 'name',
72886  *                 yField: 'data2',
72887  *                 markerConfig: {
72888  *                     type: 'circle',
72889  *                     size: 4,
72890  *                     radius: 4,
72891  *                     'stroke-width': 0
72892  *                 }
72893  *             }
72894  *         ]
72895  *     });
72896  *
72897  * In this configuration we're adding two series (or lines), one bound to the `data1`
72898  * property of the store and the other to `data3`. The type for both configurations is
72899  * `line`. The `xField` for both series is the same, the name propert of the store.
72900  * Both line series share the same axis, the left axis. You can set particular marker
72901  * configuration by adding properties onto the markerConfig object. Both series have
72902  * an object as highlight so that markers animate smoothly to the properties in highlight
72903  * when hovered. The second series has `fill=true` which means that the line will also
72904  * have an area below it of the same color.
72905  *
72906  * **Note:** In the series definition remember to explicitly set the axis to bind the
72907  * values of the line series to. This can be done by using the `axis` configuration property.
72908  */
72909 Ext.define('Ext.chart.series.Line', {
72910
72911     /* Begin Definitions */
72912
72913     extend: 'Ext.chart.series.Cartesian',
72914
72915     alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
72916
72917     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
72918
72919     /* End Definitions */
72920
72921     type: 'line',
72922
72923     alias: 'series.line',
72924
72925     /**
72926      * @cfg {String} axis
72927      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
72928      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
72929      * relative scale will be used.
72930      */
72931
72932     /**
72933      * @cfg {Number} selectionTolerance
72934      * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
72935      */
72936     selectionTolerance: 20,
72937
72938     /**
72939      * @cfg {Boolean} showMarkers
72940      * Whether markers should be displayed at the data points along the line. If true,
72941      * then the {@link #markerConfig} config item will determine the markers' styling.
72942      */
72943     showMarkers: true,
72944
72945     /**
72946      * @cfg {Object} markerConfig
72947      * The display style for the markers. Only used if {@link #showMarkers} is true.
72948      * The markerConfig is a configuration object containing the same set of properties defined in
72949      * the Sprite class. For example, if we were to set red circles as markers to the line series we could
72950      * pass the object:
72951      *
72952      <pre><code>
72953         markerConfig: {
72954             type: 'circle',
72955             radius: 4,
72956             'fill': '#f00'
72957         }
72958      </code></pre>
72959
72960      */
72961     markerConfig: {},
72962
72963     /**
72964      * @cfg {Object} style
72965      * An object containing style properties for the visualization lines and fill.
72966      * These styles will override the theme styles.  The following are valid style properties:
72967      *
72968      * - `stroke` - an rgb or hex color string for the background color of the line
72969      * - `stroke-width` - the width of the stroke (integer)
72970      * - `fill` - the background fill color string (hex or rgb), only works if {@link #fill} is `true`
72971      * - `opacity` - the opacity of the line and the fill color (decimal)
72972      *
72973      * Example usage:
72974      *
72975      *     style: {
72976      *         stroke: '#00ff00',
72977      *         'stroke-width': 10,
72978      *         fill: '#80A080',
72979      *         opacity: 0.2
72980      *     }
72981      */
72982     style: {},
72983
72984     /**
72985      * @cfg {Boolean/Number} smooth
72986      * If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
72987      * straight line segments will be drawn.
72988      *
72989      * A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in
72990      * the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.
72991      *
72992      * If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.
72993      */
72994     smooth: false,
72995
72996     /**
72997      * @private Default numeric smoothing value to be used when {@link #smooth} = true.
72998      */
72999     defaultSmoothness: 3,
73000
73001     /**
73002      * @cfg {Boolean} fill
73003      * If true, the area below the line will be filled in using the {@link #style eefill} and
73004      * {@link #style opacity} config properties. Defaults to false.
73005      */
73006     fill: false,
73007
73008     constructor: function(config) {
73009         this.callParent(arguments);
73010         var me = this,
73011             surface = me.chart.surface,
73012             shadow = me.chart.shadow,
73013             i, l;
73014         Ext.apply(me, config, {
73015             highlightCfg: {
73016                 'stroke-width': 3
73017             },
73018             shadowAttributes: [{
73019                 "stroke-width": 6,
73020                 "stroke-opacity": 0.05,
73021                 stroke: 'rgb(0, 0, 0)',
73022                 translate: {
73023                     x: 1,
73024                     y: 1
73025                 }
73026             }, {
73027                 "stroke-width": 4,
73028                 "stroke-opacity": 0.1,
73029                 stroke: 'rgb(0, 0, 0)',
73030                 translate: {
73031                     x: 1,
73032                     y: 1
73033                 }
73034             }, {
73035                 "stroke-width": 2,
73036                 "stroke-opacity": 0.15,
73037                 stroke: 'rgb(0, 0, 0)',
73038                 translate: {
73039                     x: 1,
73040                     y: 1
73041                 }
73042             }]
73043         });
73044         me.group = surface.getGroup(me.seriesId);
73045         if (me.showMarkers) {
73046             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
73047         }
73048         if (shadow) {
73049             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
73050                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
73051             }
73052         }
73053     },
73054
73055     // @private makes an average of points when there are more data points than pixels to be rendered.
73056     shrink: function(xValues, yValues, size) {
73057         // Start at the 2nd point...
73058         var len = xValues.length,
73059             ratio = Math.floor(len / size),
73060             i = 1,
73061             xSum = 0,
73062             ySum = 0,
73063             xRes = [xValues[0]],
73064             yRes = [yValues[0]];
73065
73066         for (; i < len; ++i) {
73067             xSum += xValues[i] || 0;
73068             ySum += yValues[i] || 0;
73069             if (i % ratio == 0) {
73070                 xRes.push(xSum/ratio);
73071                 yRes.push(ySum/ratio);
73072                 xSum = 0;
73073                 ySum = 0;
73074             }
73075         }
73076         return {
73077             x: xRes,
73078             y: yRes
73079         };
73080     },
73081
73082     /**
73083      * Draws the series for the current chart.
73084      */
73085     drawSeries: function() {
73086         var me = this,
73087             chart = me.chart,
73088             chartAxes = chart.axes,
73089             store = chart.getChartStore(),
73090             storeCount = store.getCount(),
73091             surface = me.chart.surface,
73092             bbox = {},
73093             group = me.group,
73094             showMarkers = me.showMarkers,
73095             markerGroup = me.markerGroup,
73096             enableShadows = chart.shadow,
73097             shadowGroups = me.shadowGroups,
73098             shadowAttributes = me.shadowAttributes,
73099             smooth = me.smooth,
73100             lnsh = shadowGroups.length,
73101             dummyPath = ["M"],
73102             path = ["M"],
73103             renderPath = ["M"],
73104             smoothPath = ["M"],
73105             markerIndex = chart.markerIndex,
73106             axes = [].concat(me.axis),
73107             shadowBarAttr,
73108             xValues = [],
73109             xValueMap = {},
73110             yValues = [],
73111             yValueMap = {},
73112             onbreak = false,
73113             storeIndices = [],
73114             markerStyle = me.markerStyle,
73115             seriesStyle = me.style,
73116             colorArrayStyle = me.colorArrayStyle,
73117             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
73118             isNumber = Ext.isNumber,
73119             seriesIdx = me.seriesIdx, 
73120             boundAxes = me.getAxesForXAndYFields(),
73121             boundXAxis = boundAxes.xAxis,
73122             boundYAxis = boundAxes.yAxis,
73123             shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
73124             x, y, prevX, prevY, firstX, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
73125             yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
73126             endLineStyle, type, count, items;
73127
73128         if (me.fireEvent('beforedraw', me) === false) {
73129             return;
73130         }
73131
73132         //if store is empty or the series is excluded in the legend then there's nothing to draw.
73133         if (!storeCount || me.seriesIsHidden) {
73134             items = this.items;
73135             if (items) {
73136                 for (i = 0, ln = items.length; i < ln; ++i) {
73137                     if (items[i].sprite) {
73138                         items[i].sprite.hide(true);
73139                     }
73140                 }
73141             }
73142             return;
73143         }
73144
73145         //prepare style objects for line and markers
73146         endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig);
73147         type = endMarkerStyle.type;
73148         delete endMarkerStyle.type;
73149         endLineStyle = seriesStyle;
73150         //if no stroke with is specified force it to 0.5 because this is
73151         //about making *lines*
73152         if (!endLineStyle['stroke-width']) {
73153             endLineStyle['stroke-width'] = 0.5;
73154         }
73155         //If we're using a time axis and we need to translate the points,
73156         //then reuse the first markers as the last markers.
73157         if (markerIndex && markerGroup && markerGroup.getCount()) {
73158             for (i = 0; i < markerIndex; i++) {
73159                 marker = markerGroup.getAt(i);
73160                 markerGroup.remove(marker);
73161                 markerGroup.add(marker);
73162                 markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
73163                 marker.setAttributes({
73164                     x: 0,
73165                     y: 0,
73166                     translate: {
73167                         x: markerAux.attr.translation.x,
73168                         y: markerAux.attr.translation.y
73169                     }
73170                 }, true);
73171             }
73172         }
73173
73174         me.unHighlightItem();
73175         me.cleanHighlights();
73176
73177         me.setBBox();
73178         bbox = me.bbox;
73179         me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
73180         for (i = 0, ln = axes.length; i < ln; i++) {
73181             axis = chartAxes.get(axes[i]);
73182             if (axis) {
73183                 ends = axis.calcEnds();
73184                 if (axis.position == 'top' || axis.position == 'bottom') {
73185                     minX = ends.from;
73186                     maxX = ends.to;
73187                 }
73188                 else {
73189                     minY = ends.from;
73190                     maxY = ends.to;
73191                 }
73192             }
73193         }
73194         // If a field was specified without a corresponding axis, create one to get bounds
73195         //only do this for the axis where real values are bound (that's why we check for
73196         //me.axis)
73197         if (me.xField && !isNumber(minX) &&
73198             (boundXAxis == 'bottom' || boundXAxis == 'top') && 
73199             !chartAxes.get(boundXAxis)) {
73200             axis = Ext.create('Ext.chart.axis.Axis', {
73201                 chart: chart,
73202                 fields: [].concat(me.xField)
73203             }).calcEnds();
73204             minX = axis.from;
73205             maxX = axis.to;
73206         }
73207         if (me.yField && !isNumber(minY) &&
73208             (boundYAxis == 'right' || boundYAxis == 'left') &&
73209             !chartAxes.get(boundYAxis)) {
73210             axis = Ext.create('Ext.chart.axis.Axis', {
73211                 chart: chart,
73212                 fields: [].concat(me.yField)
73213             }).calcEnds();
73214             minY = axis.from;
73215             maxY = axis.to;
73216         }
73217         if (isNaN(minX)) {
73218             minX = 0;
73219             xScale = bbox.width / ((storeCount - 1) || 1);
73220         }
73221         else {
73222             xScale = bbox.width / ((maxX - minX) || (storeCount -1) || 1);
73223         }
73224
73225         if (isNaN(minY)) {
73226             minY = 0;
73227             yScale = bbox.height / ((storeCount - 1) || 1);
73228         }
73229         else {
73230             yScale = bbox.height / ((maxY - minY) || (storeCount - 1) || 1);
73231         }
73232
73233         // Extract all x and y values from the store
73234         me.eachRecord(function(record, i) {
73235             xValue = record.get(me.xField);
73236
73237             // Ensure a value
73238             if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)
73239                 //set as uniform distribution if the axis is a category axis.
73240                 || boundXAxis && chartAxes.get(boundXAxis) && chartAxes.get(boundXAxis).type == 'Category') {
73241                     if (xValue in xValueMap) {
73242                         xValue = xValueMap[xValue];
73243                     } else {
73244                         xValue = xValueMap[xValue] = i;
73245                     }
73246             }
73247
73248             // Filter out values that don't fit within the pan/zoom buffer area
73249             yValue = record.get(me.yField);
73250             //skip undefined values
73251             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
73252                 if (Ext.isDefined(Ext.global.console)) {
73253                     Ext.global.console.warn("[Ext.chart.series.Line]  Skipping a store element with an undefined value at ", record, xValue, yValue);
73254                 }
73255                 return;
73256             }
73257             // Ensure a value
73258             if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)
73259                 //set as uniform distribution if the axis is a category axis.
73260                 || boundYAxis && chartAxes.get(boundYAxis) && chartAxes.get(boundYAxis).type == 'Category') {
73261                 yValue = i;
73262             }
73263             storeIndices.push(i);
73264             xValues.push(xValue);
73265             yValues.push(yValue);
73266         });
73267
73268         ln = xValues.length;
73269         if (ln > bbox.width) {
73270             coords = me.shrink(xValues, yValues, bbox.width);
73271             xValues = coords.x;
73272             yValues = coords.y;
73273         }
73274
73275         me.items = [];
73276
73277         count = 0;
73278         ln = xValues.length;
73279         for (i = 0; i < ln; i++) {
73280             xValue = xValues[i];
73281             yValue = yValues[i];
73282             if (yValue === false) {
73283                 if (path.length == 1) {
73284                     path = [];
73285                 }
73286                 onbreak = true;
73287                 me.items.push(false);
73288                 continue;
73289             } else {
73290                 x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
73291                 y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
73292                 if (onbreak) {
73293                     onbreak = false;
73294                     path.push('M');
73295                 }
73296                 path = path.concat([x, y]);
73297             }
73298             if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
73299                 firstY = y;
73300                 firstX = x;
73301             }
73302             // If this is the first line, create a dummypath to animate in from.
73303             if (!me.line || chart.resizing) {
73304                 dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
73305             }
73306
73307             // When resizing, reset before animating
73308             if (chart.animate && chart.resizing && me.line) {
73309                 me.line.setAttributes({
73310                     path: dummyPath
73311                 }, true);
73312                 if (me.fillPath) {
73313                     me.fillPath.setAttributes({
73314                         path: dummyPath,
73315                         opacity: 0.2
73316                     }, true);
73317                 }
73318                 if (me.line.shadows) {
73319                     shadows = me.line.shadows;
73320                     for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
73321                         shadow = shadows[j];
73322                         shadow.setAttributes({
73323                             path: dummyPath
73324                         }, true);
73325                     }
73326                 }
73327             }
73328             if (showMarkers) {
73329                 marker = markerGroup.getAt(count++);
73330                 if (!marker) {
73331                     marker = Ext.chart.Shape[type](surface, Ext.apply({
73332                         group: [group, markerGroup],
73333                         x: 0, y: 0,
73334                         translate: {
73335                             x: +(prevX || x),
73336                             y: prevY || (bbox.y + bbox.height / 2)
73337                         },
73338                         value: '"' + xValue + ', ' + yValue + '"',
73339                         zIndex: 4000
73340                     }, endMarkerStyle));
73341                     marker._to = {
73342                         translate: {
73343                             x: +x,
73344                             y: +y
73345                         }
73346                     };
73347                 } else {
73348                     marker.setAttributes({
73349                         value: '"' + xValue + ', ' + yValue + '"',
73350                         x: 0, y: 0,
73351                         hidden: false
73352                     }, true);
73353                     marker._to = {
73354                         translate: {
73355                             x: +x, 
73356                             y: +y
73357                         }
73358                     };
73359                 }
73360             }
73361             me.items.push({
73362                 series: me,
73363                 value: [xValue, yValue],
73364                 point: [x, y],
73365                 sprite: marker,
73366                 storeItem: store.getAt(storeIndices[i])
73367             });
73368             prevX = x;
73369             prevY = y;
73370         }
73371
73372         if (path.length <= 1) {
73373             //nothing to be rendered
73374             return;
73375         }
73376
73377         if (me.smooth) {
73378             smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
73379         }
73380
73381         renderPath = smooth ? smoothPath : path;
73382
73383         //Correct path if we're animating timeAxis intervals
73384         if (chart.markerIndex && me.previousPath) {
73385             fromPath = me.previousPath;
73386             if (!smooth) {
73387                 Ext.Array.erase(fromPath, 1, 2);
73388             }
73389         } else {
73390             fromPath = path;
73391         }
73392
73393         // Only create a line if one doesn't exist.
73394         if (!me.line) {
73395             me.line = surface.add(Ext.apply({
73396                 type: 'path',
73397                 group: group,
73398                 path: dummyPath,
73399                 stroke: endLineStyle.stroke || endLineStyle.fill
73400             }, endLineStyle || {}));
73401
73402             if (enableShadows) {
73403                 me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);
73404             }
73405
73406             //unset fill here (there's always a default fill withing the themes).
73407             me.line.setAttributes({
73408                 fill: 'none',
73409                 zIndex: 3000
73410             });
73411             if (!endLineStyle.stroke && colorArrayLength) {
73412                 me.line.setAttributes({
73413                     stroke: colorArrayStyle[seriesIdx % colorArrayLength]
73414                 }, true);
73415             }
73416             if (enableShadows) {
73417                 //create shadows
73418                 shadows = me.line.shadows = [];
73419                 for (shindex = 0; shindex < lnsh; shindex++) {
73420                     shadowBarAttr = shadowAttributes[shindex];
73421                     shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
73422                     shadow = surface.add(Ext.apply({}, {
73423                         type: 'path',
73424                         group: shadowGroups[shindex]
73425                     }, shadowBarAttr));
73426                     shadows.push(shadow);
73427                 }
73428             }
73429         }
73430         if (me.fill) {
73431             fillPath = renderPath.concat([
73432                 ["L", x, bbox.y + bbox.height],
73433                 ["L", firstX, bbox.y + bbox.height],
73434                 ["L", firstX, firstY]
73435             ]);
73436             if (!me.fillPath) {
73437                 me.fillPath = surface.add({
73438                     group: group,
73439                     type: 'path',
73440                     opacity: endLineStyle.opacity || 0.3,
73441                     fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
73442                     path: dummyPath
73443                 });
73444             }
73445         }
73446         markerCount = showMarkers && markerGroup.getCount();
73447         if (chart.animate) {
73448             fill = me.fill;
73449             line = me.line;
73450             //Add renderer to line. There is not unique record associated with this.
73451             rendererAttributes = me.renderer(line, false, { path: renderPath }, i, store);
73452             Ext.apply(rendererAttributes, endLineStyle || {}, {
73453                 stroke: endLineStyle.stroke || endLineStyle.fill
73454             });
73455             //fill should not be used here but when drawing the special fill path object
73456             delete rendererAttributes.fill;
73457             line.show(true);
73458             if (chart.markerIndex && me.previousPath) {
73459                 me.animation = animation = me.onAnimate(line, {
73460                     to: rendererAttributes,
73461                     from: {
73462                         path: fromPath
73463                     }
73464                 });
73465             } else {
73466                 me.animation = animation = me.onAnimate(line, {
73467                     to: rendererAttributes
73468                 });
73469             }
73470             //animate shadows
73471             if (enableShadows) {
73472                 shadows = line.shadows;
73473                 for(j = 0; j < lnsh; j++) {
73474                     shadows[j].show(true);
73475                     if (chart.markerIndex && me.previousPath) {
73476                         me.onAnimate(shadows[j], {
73477                             to: { path: renderPath },
73478                             from: { path: fromPath }
73479                         });
73480                     } else {
73481                         me.onAnimate(shadows[j], {
73482                             to: { path: renderPath }
73483                         });
73484                     }
73485                 }
73486             }
73487             //animate fill path
73488             if (fill) {
73489                 me.fillPath.show(true);
73490                 me.onAnimate(me.fillPath, {
73491                     to: Ext.apply({}, {
73492                         path: fillPath,
73493                         fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
73494                         'stroke-width': 0
73495                     }, endLineStyle || {})
73496                 });
73497             }
73498             //animate markers
73499             if (showMarkers) {
73500                 count = 0;
73501                 for(i = 0; i < ln; i++) {
73502                     if (me.items[i]) {
73503                         item = markerGroup.getAt(count++);
73504                         if (item) {
73505                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
73506                             me.onAnimate(item, {
73507                                 to: Ext.apply(rendererAttributes, endMarkerStyle || {})
73508                             });
73509                             item.show(true);
73510                         }
73511                     }
73512                 }
73513                 for(; count < markerCount; count++) {
73514                     item = markerGroup.getAt(count);
73515                     item.hide(true);
73516                 }
73517 //                for(i = 0; i < (chart.markerIndex || 0)-1; i++) {
73518 //                    item = markerGroup.getAt(i);
73519 //                    item.hide(true);
73520 //                }
73521             }
73522         } else {
73523             rendererAttributes = me.renderer(me.line, false, { path: renderPath, hidden: false }, i, store);
73524             Ext.apply(rendererAttributes, endLineStyle || {}, {
73525                 stroke: endLineStyle.stroke || endLineStyle.fill
73526             });
73527             //fill should not be used here but when drawing the special fill path object
73528             delete rendererAttributes.fill;
73529             me.line.setAttributes(rendererAttributes, true);
73530             //set path for shadows
73531             if (enableShadows) {
73532                 shadows = me.line.shadows;
73533                 for(j = 0; j < lnsh; j++) {
73534                     shadows[j].setAttributes({
73535                         path: renderPath,
73536                         hidden: false
73537                     }, true);
73538                 }
73539             }
73540             if (me.fill) {
73541                 me.fillPath.setAttributes({
73542                     path: fillPath,
73543                     hidden: false
73544                 }, true);
73545             }
73546             if (showMarkers) {
73547                 count = 0;
73548                 for(i = 0; i < ln; i++) {
73549                     if (me.items[i]) {
73550                         item = markerGroup.getAt(count++);
73551                         if (item) {
73552                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
73553                             item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
73554                             item.show(true);
73555                         }
73556                     }
73557                 }
73558                 for(; count < markerCount; count++) {
73559                     item = markerGroup.getAt(count);
73560                     item.hide(true);
73561                 }
73562             }
73563         }
73564
73565         if (chart.markerIndex) {
73566             if (me.smooth) {
73567                 Ext.Array.erase(path, 1, 2);
73568             } else {
73569                 Ext.Array.splice(path, 1, 0, path[1], path[2]);
73570             }
73571             me.previousPath = path;
73572         }
73573         me.renderLabels();
73574         me.renderCallouts();
73575
73576         me.fireEvent('draw', me);
73577     },
73578
73579     // @private called when a label is to be created.
73580     onCreateLabel: function(storeItem, item, i, display) {
73581         var me = this,
73582             group = me.labelsGroup,
73583             config = me.label,
73584             bbox = me.bbox,
73585             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
73586
73587         return me.chart.surface.add(Ext.apply({
73588             'type': 'text',
73589             'text-anchor': 'middle',
73590             'group': group,
73591             'x': item.point[0],
73592             'y': bbox.y + bbox.height / 2
73593         }, endLabelStyle || {}));
73594     },
73595
73596     // @private called when a label is to be created.
73597     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
73598         var me = this,
73599             chart = me.chart,
73600             resizing = chart.resizing,
73601             config = me.label,
73602             format = config.renderer,
73603             field = config.field,
73604             bbox = me.bbox,
73605             x = item.point[0],
73606             y = item.point[1],
73607             radius = item.sprite.attr.radius,
73608             bb, width, height;
73609
73610         label.setAttributes({
73611             text: format(storeItem.get(field)),
73612             hidden: true
73613         }, true);
73614
73615         if (display == 'rotate') {
73616             label.setAttributes({
73617                 'text-anchor': 'start',
73618                 'rotation': {
73619                     x: x,
73620                     y: y,
73621                     degrees: -45
73622                 }
73623             }, true);
73624             //correct label position to fit into the box
73625             bb = label.getBBox();
73626             width = bb.width;
73627             height = bb.height;
73628             x = x < bbox.x? bbox.x : x;
73629             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
73630             y = (y - height < bbox.y)? bbox.y + height : y;
73631
73632         } else if (display == 'under' || display == 'over') {
73633             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
73634             bb = item.sprite.getBBox();
73635             bb.width = bb.width || (radius * 2);
73636             bb.height = bb.height || (radius * 2);
73637             y = y + (display == 'over'? -bb.height : bb.height);
73638             //correct label position to fit into the box
73639             bb = label.getBBox();
73640             width = bb.width/2;
73641             height = bb.height/2;
73642             x = x - width < bbox.x? bbox.x + width : x;
73643             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
73644             y = y - height < bbox.y? bbox.y + height : y;
73645             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
73646         }
73647
73648         if (me.chart.animate && !me.chart.resizing) {
73649             label.show(true);
73650             me.onAnimate(label, {
73651                 to: {
73652                     x: x,
73653                     y: y
73654                 }
73655             });
73656         } else {
73657             label.setAttributes({
73658                 x: x,
73659                 y: y
73660             }, true);
73661             if (resizing && me.animation) {
73662                 me.animation.on('afteranimate', function() {
73663                     label.show(true);
73664                 });
73665             } else {
73666                 label.show(true);
73667             }
73668         }
73669     },
73670
73671     //@private Overriding highlights.js highlightItem method.
73672     highlightItem: function() {
73673         var me = this;
73674         me.callParent(arguments);
73675         if (me.line && !me.highlighted) {
73676             if (!('__strokeWidth' in me.line)) {
73677                 me.line.__strokeWidth = me.line.attr['stroke-width'] || 0;
73678             }
73679             if (me.line.__anim) {
73680                 me.line.__anim.paused = true;
73681             }
73682             me.line.__anim = Ext.create('Ext.fx.Anim', {
73683                 target: me.line,
73684                 to: {
73685                     'stroke-width': me.line.__strokeWidth + 3
73686                 }
73687             });
73688             me.highlighted = true;
73689         }
73690     },
73691
73692     //@private Overriding highlights.js unHighlightItem method.
73693     unHighlightItem: function() {
73694         var me = this;
73695         me.callParent(arguments);
73696         if (me.line && me.highlighted) {
73697             me.line.__anim = Ext.create('Ext.fx.Anim', {
73698                 target: me.line,
73699                 to: {
73700                     'stroke-width': me.line.__strokeWidth
73701                 }
73702             });
73703             me.highlighted = false;
73704         }
73705     },
73706
73707     //@private called when a callout needs to be placed.
73708     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
73709         if (!display) {
73710             return;
73711         }
73712
73713         var me = this,
73714             chart = me.chart,
73715             surface = chart.surface,
73716             resizing = chart.resizing,
73717             config = me.callouts,
73718             items = me.items,
73719             prev = i == 0? false : items[i -1].point,
73720             next = (i == items.length -1)? false : items[i +1].point,
73721             cur = [+item.point[0], +item.point[1]],
73722             dir, norm, normal, a, aprev, anext,
73723             offsetFromViz = config.offsetFromViz || 30,
73724             offsetToSide = config.offsetToSide || 10,
73725             offsetBox = config.offsetBox || 3,
73726             boxx, boxy, boxw, boxh,
73727             p, clipRect = me.clipRect,
73728             bbox = {
73729                 width: config.styles.width || 10,
73730                 height: config.styles.height || 10
73731             },
73732             x, y;
73733
73734         //get the right two points
73735         if (!prev) {
73736             prev = cur;
73737         }
73738         if (!next) {
73739             next = cur;
73740         }
73741         a = (next[1] - prev[1]) / (next[0] - prev[0]);
73742         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
73743         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
73744
73745         norm = Math.sqrt(1 + a * a);
73746         dir = [1 / norm, a / norm];
73747         normal = [-dir[1], dir[0]];
73748
73749         //keep the label always on the outer part of the "elbow"
73750         if (aprev > 0 && anext < 0 && normal[1] < 0
73751             || aprev < 0 && anext > 0 && normal[1] > 0) {
73752             normal[0] *= -1;
73753             normal[1] *= -1;
73754         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0
73755                    || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
73756             normal[0] *= -1;
73757             normal[1] *= -1;
73758         }
73759         //position
73760         x = cur[0] + normal[0] * offsetFromViz;
73761         y = cur[1] + normal[1] * offsetFromViz;
73762
73763         //box position and dimensions
73764         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
73765         boxy = y - bbox.height /2 - offsetBox;
73766         boxw = bbox.width + 2 * offsetBox;
73767         boxh = bbox.height + 2 * offsetBox;
73768
73769         //now check if we're out of bounds and invert the normal vector correspondingly
73770         //this may add new overlaps between labels (but labels won't be out of bounds).
73771         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
73772             normal[0] *= -1;
73773         }
73774         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
73775             normal[1] *= -1;
73776         }
73777
73778         //update positions
73779         x = cur[0] + normal[0] * offsetFromViz;
73780         y = cur[1] + normal[1] * offsetFromViz;
73781
73782         //update box position and dimensions
73783         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
73784         boxy = y - bbox.height /2 - offsetBox;
73785         boxw = bbox.width + 2 * offsetBox;
73786         boxh = bbox.height + 2 * offsetBox;
73787
73788         if (chart.animate) {
73789             //set the line from the middle of the pie to the box.
73790             me.onAnimate(callout.lines, {
73791                 to: {
73792                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
73793                 }
73794             });
73795             //set component position
73796             if (callout.panel) {
73797                 callout.panel.setPosition(boxx, boxy, true);
73798             }
73799         }
73800         else {
73801             //set the line from the middle of the pie to the box.
73802             callout.lines.setAttributes({
73803                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
73804             }, true);
73805             //set component position
73806             if (callout.panel) {
73807                 callout.panel.setPosition(boxx, boxy);
73808             }
73809         }
73810         for (p in callout) {
73811             callout[p].show(true);
73812         }
73813     },
73814
73815     isItemInPoint: function(x, y, item, i) {
73816         var me = this,
73817             items = me.items,
73818             tolerance = me.selectionTolerance,
73819             result = null,
73820             prevItem,
73821             nextItem,
73822             prevPoint,
73823             nextPoint,
73824             ln,
73825             x1,
73826             y1,
73827             x2,
73828             y2,
73829             xIntersect,
73830             yIntersect,
73831             dist1, dist2, dist, midx, midy,
73832             sqrt = Math.sqrt, abs = Math.abs;
73833
73834         nextItem = items[i];
73835         prevItem = i && items[i - 1];
73836
73837         if (i >= ln) {
73838             prevItem = items[ln - 1];
73839         }
73840         prevPoint = prevItem && prevItem.point;
73841         nextPoint = nextItem && nextItem.point;
73842         x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
73843         y1 = prevItem ? prevPoint[1] : nextPoint[1];
73844         x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
73845         y2 = nextItem ? nextPoint[1] : prevPoint[1];
73846         dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
73847         dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
73848         dist = Math.min(dist1, dist2);
73849
73850         if (dist <= tolerance) {
73851             return dist == dist1? prevItem : nextItem;
73852         }
73853         return false;
73854     },
73855
73856     // @private toggle visibility of all series elements (markers, sprites).
73857     toggleAll: function(show) {
73858         var me = this,
73859             i, ln, shadow, shadows;
73860         if (!show) {
73861             Ext.chart.series.Cartesian.prototype.hideAll.call(me);
73862         }
73863         else {
73864             Ext.chart.series.Cartesian.prototype.showAll.call(me);
73865         }
73866         if (me.line) {
73867             me.line.setAttributes({
73868                 hidden: !show
73869             }, true);
73870             //hide shadows too
73871             if (me.line.shadows) {
73872                 for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
73873                     shadow = shadows[i];
73874                     shadow.setAttributes({
73875                         hidden: !show
73876                     }, true);
73877                 }
73878             }
73879         }
73880         if (me.fillPath) {
73881             me.fillPath.setAttributes({
73882                 hidden: !show
73883             }, true);
73884         }
73885     },
73886
73887     // @private hide all series elements (markers, sprites).
73888     hideAll: function() {
73889         this.toggleAll(false);
73890     },
73891
73892     // @private hide all series elements (markers, sprites).
73893     showAll: function() {
73894         this.toggleAll(true);
73895     }
73896 });
73897
73898 /**
73899  * @class Ext.chart.series.Pie
73900  * @extends Ext.chart.series.Series
73901  *
73902  * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different
73903  * categories that also have a meaning as a whole.
73904  * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart
73905  * documentation for more information. A typical configuration object for the pie series could be:
73906  *
73907  *     @example
73908  *     var store = Ext.create('Ext.data.JsonStore', {
73909  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
73910  *         data: [
73911  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
73912  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
73913  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
73914  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
73915  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
73916  *         ]
73917  *     });
73918  *
73919  *     Ext.create('Ext.chart.Chart', {
73920  *         renderTo: Ext.getBody(),
73921  *         width: 500,
73922  *         height: 350,
73923  *         animate: true,
73924  *         store: store,
73925  *         theme: 'Base:gradients',
73926  *         series: [{
73927  *             type: 'pie',
73928  *             field: 'data1',
73929  *             showInLegend: true,
73930  *             tips: {
73931  *                 trackMouse: true,
73932  *                 width: 140,
73933  *                 height: 28,
73934  *                 renderer: function(storeItem, item) {
73935  *                     // calculate and display percentage on hover
73936  *                     var total = 0;
73937  *                     store.each(function(rec) {
73938  *                         total += rec.get('data1');
73939  *                     });
73940  *                     this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%');
73941  *                 }
73942  *             },
73943  *             highlight: {
73944  *                 segment: {
73945  *                     margin: 20
73946  *                 }
73947  *             },
73948  *             label: {
73949  *                 field: 'name',
73950  *                 display: 'rotate',
73951  *                 contrast: true,
73952  *                 font: '18px Arial'
73953  *             }
73954  *         }]
73955  *     });
73956  *
73957  * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options
73958  * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item.
73959  *
73960  * 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
73961  * 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.
73962  *
73963  * 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
73964  * and size through the `font` parameter.
73965  *
73966  * @xtype pie
73967  */
73968 Ext.define('Ext.chart.series.Pie', {
73969
73970     /* Begin Definitions */
73971
73972     alternateClassName: ['Ext.chart.PieSeries', 'Ext.chart.PieChart'],
73973
73974     extend: 'Ext.chart.series.Series',
73975
73976     /* End Definitions */
73977
73978     type: "pie",
73979
73980     alias: 'series.pie',
73981
73982     rad: Math.PI / 180,
73983
73984     /**
73985      * @cfg {Number} highlightDuration
73986      * The duration for the pie slice highlight effect.
73987      */
73988     highlightDuration: 150,
73989
73990     /**
73991      * @cfg {String} angleField (required)
73992      * The store record field name to be used for the pie angles.
73993      * The values bound to this field name must be positive real numbers.
73994      */
73995     angleField: false,
73996
73997     /**
73998      * @cfg {String} lengthField
73999      * The store record field name to be used for the pie slice lengths.
74000      * The values bound to this field name must be positive real numbers.
74001      */
74002     lengthField: false,
74003
74004     /**
74005      * @cfg {Boolean/Number} donut
74006      * Whether to set the pie chart as donut chart.
74007      * Default's false. Can be set to a particular percentage to set the radius
74008      * of the donut chart.
74009      */
74010     donut: false,
74011
74012     /**
74013      * @cfg {Boolean} showInLegend
74014      * Whether to add the pie chart elements as legend items. Default's false.
74015      */
74016     showInLegend: false,
74017
74018     /**
74019      * @cfg {Array} colorSet
74020      * An array of color values which will be used, in order, as the pie slice fill colors.
74021      */
74022
74023     /**
74024      * @cfg {Object} style
74025      * An object containing styles for overriding series styles from Theming.
74026      */
74027     style: {},
74028
74029     constructor: function(config) {
74030         this.callParent(arguments);
74031         var me = this,
74032             chart = me.chart,
74033             surface = chart.surface,
74034             store = chart.store,
74035             shadow = chart.shadow, i, l, cfg;
74036         Ext.applyIf(me, {
74037             highlightCfg: {
74038                 segment: {
74039                     margin: 20
74040                 }
74041             }
74042         });
74043         Ext.apply(me, config, {
74044             shadowAttributes: [{
74045                 "stroke-width": 6,
74046                 "stroke-opacity": 1,
74047                 stroke: 'rgb(200, 200, 200)',
74048                 translate: {
74049                     x: 1.2,
74050                     y: 2
74051                 }
74052             },
74053             {
74054                 "stroke-width": 4,
74055                 "stroke-opacity": 1,
74056                 stroke: 'rgb(150, 150, 150)',
74057                 translate: {
74058                     x: 0.9,
74059                     y: 1.5
74060                 }
74061             },
74062             {
74063                 "stroke-width": 2,
74064                 "stroke-opacity": 1,
74065                 stroke: 'rgb(100, 100, 100)',
74066                 translate: {
74067                     x: 0.6,
74068                     y: 1
74069                 }
74070             }]
74071         });
74072         me.group = surface.getGroup(me.seriesId);
74073         if (shadow) {
74074             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
74075                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
74076             }
74077         }
74078         surface.customAttributes.segment = function(opt) {
74079             return me.getSegment(opt);
74080         };
74081         me.__excludes = me.__excludes || [];
74082     },
74083
74084     //@private updates some onbefore render parameters.
74085     initialize: function() {
74086         var me = this,
74087             store = me.chart.getChartStore();
74088         //Add yFields to be used in Legend.js
74089         me.yField = [];
74090         if (me.label.field) {
74091             store.each(function(rec) {
74092                 me.yField.push(rec.get(me.label.field));
74093             });
74094         }
74095     },
74096
74097     // @private returns an object with properties for a PieSlice.
74098     getSegment: function(opt) {
74099         var me = this,
74100             rad = me.rad,
74101             cos = Math.cos,
74102             sin = Math.sin,
74103             x = me.centerX,
74104             y = me.centerY,
74105             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
74106             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
74107             x5 = 0, y5 = 0, x6 = 0, y6 = 0,
74108             delta = 1e-2,
74109             startAngle = opt.startAngle,
74110             endAngle = opt.endAngle,
74111             midAngle = (startAngle + endAngle) / 2 * rad,
74112             margin = opt.margin || 0,
74113             a1 = Math.min(startAngle, endAngle) * rad,
74114             a2 = Math.max(startAngle, endAngle) * rad,
74115             c1 = cos(a1), s1 = sin(a1),
74116             c2 = cos(a2), s2 = sin(a2),
74117             cm = cos(midAngle), sm = sin(midAngle),
74118             flag = 0, hsqr2 = 0.7071067811865476; // sqrt(0.5)
74119
74120         if (a2 - a1 < delta) {
74121             return {path: ""};
74122         }
74123
74124         if (margin !== 0) {
74125             x += margin * cm;
74126             y += margin * sm;
74127         }
74128
74129         x2 = x + opt.endRho * c1;
74130         y2 = y + opt.endRho * s1;
74131
74132         x4 = x + opt.endRho * c2;
74133         y4 = y + opt.endRho * s2;
74134
74135         if (Math.abs(x2 - x4) + Math.abs(y2 - y4) < delta) {
74136             cm = hsqr2;
74137             sm = -hsqr2;
74138             flag = 1;
74139         }
74140
74141         x6 = x + opt.endRho * cm;
74142         y6 = y + opt.endRho * sm;
74143
74144         // TODO(bei): It seems that the canvas engine cannot render half circle command correctly on IE.
74145         // Better fix the VML engine for half circles.
74146
74147         if (opt.startRho !== 0) {
74148             x1 = x + opt.startRho * c1;
74149             y1 = y + opt.startRho * s1;
74150     
74151             x3 = x + opt.startRho * c2;
74152             y3 = y + opt.startRho * s2;
74153     
74154             x5 = x + opt.startRho * cm;
74155             y5 = y + opt.startRho * sm;
74156
74157             return {
74158                 path: [
74159                     ["M", x2, y2],
74160                     ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
74161                     ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
74162                     ["L", x3, y3],
74163                     ["A", opt.startRho, opt.startRho, 0, flag, 0, x5, y5], ["L", x5, y5],
74164                     ["A", opt.startRho, opt.startRho, 0, 0, 0, x1, y1], ["L", x1, y1],
74165                     ["Z"]
74166                 ]
74167             };
74168         } else {
74169             return {
74170                 path: [
74171                     ["M", x, y],
74172                     ["L", x2, y2],
74173                     ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
74174                     ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
74175                     ["L", x, y],
74176                     ["Z"]
74177                 ]
74178             };
74179         }
74180     },
74181
74182     // @private utility function to calculate the middle point of a pie slice.
74183     calcMiddle: function(item) {
74184         var me = this,
74185             rad = me.rad,
74186             slice = item.slice,
74187             x = me.centerX,
74188             y = me.centerY,
74189             startAngle = slice.startAngle,
74190             endAngle = slice.endAngle,
74191             donut = +me.donut,
74192             midAngle = -(startAngle + endAngle) * rad / 2,
74193             r = (item.endRho + item.startRho) / 2,
74194             xm = x + r * Math.cos(midAngle),
74195             ym = y - r * Math.sin(midAngle);
74196
74197         item.middle = {
74198             x: xm,
74199             y: ym
74200         };
74201     },
74202
74203     /**
74204      * Draws the series for the current chart.
74205      */
74206     drawSeries: function() {
74207         var me = this,
74208             store = me.chart.getChartStore(),
74209             group = me.group,
74210             animate = me.chart.animate,
74211             field = me.angleField || me.field || me.xField,
74212             lenField = [].concat(me.lengthField),
74213             totalLenField = 0,
74214             colors = me.colorSet,
74215             chart = me.chart,
74216             surface = chart.surface,
74217             chartBBox = chart.chartBBox,
74218             enableShadows = chart.shadow,
74219             shadowGroups = me.shadowGroups,
74220             shadowAttributes = me.shadowAttributes,
74221             lnsh = shadowGroups.length,
74222             rad = me.rad,
74223             layers = lenField.length,
74224             rhoAcum = 0,
74225             donut = +me.donut,
74226             layerTotals = [],
74227             values = {},
74228             fieldLength,
74229             items = [],
74230             passed = false,
74231             totalField = 0,
74232             maxLenField = 0,
74233             cut = 9,
74234             defcut = true,
74235             angle = 0,
74236             seriesStyle = me.seriesStyle,
74237             seriesLabelStyle = me.seriesLabelStyle,
74238             colorArrayStyle = me.colorArrayStyle,
74239             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
74240             gutterX = chart.maxGutter[0],
74241             gutterY = chart.maxGutter[1],
74242             abs = Math.abs,
74243             rendererAttributes,
74244             shadowGroup,
74245             shadowAttr,
74246             shadows,
74247             shadow,
74248             shindex,
74249             centerX,
74250             centerY,
74251             deltaRho,
74252             first = 0,
74253             slice,
74254             slices,
74255             sprite,
74256             value,
74257             item,
74258             lenValue,
74259             ln,
74260             record,
74261             i,
74262             j,
74263             startAngle,
74264             endAngle,
74265             middleAngle,
74266             sliceLength,
74267             path,
74268             p,
74269             spriteOptions, bbox;
74270
74271         Ext.apply(seriesStyle, me.style || {});
74272
74273         me.setBBox();
74274         bbox = me.bbox;
74275
74276         //override theme colors
74277         if (me.colorSet) {
74278             colorArrayStyle = me.colorSet;
74279             colorArrayLength = colorArrayStyle.length;
74280         }
74281
74282         //if not store or store is empty then there's nothing to draw
74283         if (!store || !store.getCount()) {
74284             return;
74285         }
74286
74287         me.unHighlightItem();
74288         me.cleanHighlights();
74289
74290         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
74291         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
74292         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
74293         me.slices = slices = [];
74294         me.items = items = [];
74295
74296         store.each(function(record, i) {
74297             if (this.__excludes && this.__excludes[i]) {
74298                 //hidden series
74299                 return;
74300             }
74301             totalField += +record.get(field);
74302             if (lenField[0]) {
74303                 for (j = 0, totalLenField = 0; j < layers; j++) {
74304                     totalLenField += +record.get(lenField[j]);
74305                 }
74306                 layerTotals[i] = totalLenField;
74307                 maxLenField = Math.max(maxLenField, totalLenField);
74308             }
74309         }, this);
74310
74311         totalField = totalField || 1;
74312         store.each(function(record, i) {
74313             if (this.__excludes && this.__excludes[i]) {
74314                 value = 0;
74315             } else {
74316                 value = record.get(field);
74317                 if (first == 0) {
74318                     first = 1;
74319                 }
74320             }
74321
74322             // First slice
74323             if (first == 1) {
74324                 first = 2;
74325                 me.firstAngle = angle = 360 * value / totalField / 2;
74326                 for (j = 0; j < i; j++) {
74327                     slices[j].startAngle = slices[j].endAngle = me.firstAngle;
74328                 }
74329             }
74330             
74331             endAngle = angle - 360 * value / totalField;
74332             slice = {
74333                 series: me,
74334                 value: value,
74335                 startAngle: angle,
74336                 endAngle: endAngle,
74337                 storeItem: record
74338             };
74339             if (lenField[0]) {
74340                 lenValue = layerTotals[i];
74341                 slice.rho = me.radius * (lenValue / maxLenField);
74342             } else {
74343                 slice.rho = me.radius;
74344             }
74345             slices[i] = slice;
74346             angle = endAngle;
74347         }, me);
74348         //do all shadows first.
74349         if (enableShadows) {
74350             for (i = 0, ln = slices.length; i < ln; i++) {
74351                 slice = slices[i];
74352                 slice.shadowAttrs = [];
74353                 for (j = 0, rhoAcum = 0, shadows = []; j < layers; j++) {
74354                     sprite = group.getAt(i * layers + j);
74355                     deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
74356                     //set pie slice properties
74357                     rendererAttributes = {
74358                         segment: {
74359                             startAngle: slice.startAngle,
74360                             endAngle: slice.endAngle,
74361                             margin: 0,
74362                             rho: slice.rho,
74363                             startRho: rhoAcum + (deltaRho * donut / 100),
74364                             endRho: rhoAcum + deltaRho
74365                         },
74366                         hidden: !slice.value && (slice.startAngle % 360) == (slice.endAngle % 360)
74367                     };
74368                     //create shadows
74369                     for (shindex = 0, shadows = []; shindex < lnsh; shindex++) {
74370                         shadowAttr = shadowAttributes[shindex];
74371                         shadow = shadowGroups[shindex].getAt(i);
74372                         if (!shadow) {
74373                             shadow = chart.surface.add(Ext.apply({}, {
74374                                 type: 'path',
74375                                 group: shadowGroups[shindex],
74376                                 strokeLinejoin: "round"
74377                             }, rendererAttributes, shadowAttr));
74378                         }
74379                         if (animate) {
74380                             shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply({}, rendererAttributes, shadowAttr), i, store);
74381                             me.onAnimate(shadow, {
74382                                 to: shadowAttr
74383                             });
74384                         } else {
74385                             shadowAttr = me.renderer(shadow, store.getAt(i), shadowAttr, i, store);
74386                             shadow.setAttributes(shadowAttr, true);
74387                         }
74388                         shadows.push(shadow);
74389                     }
74390                     slice.shadowAttrs[j] = shadows;
74391                 }
74392             }
74393         }
74394         //do pie slices after.
74395         for (i = 0, ln = slices.length; i < ln; i++) {
74396             slice = slices[i];
74397             for (j = 0, rhoAcum = 0; j < layers; j++) {
74398                 sprite = group.getAt(i * layers + j);
74399                 deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
74400                 //set pie slice properties
74401                 rendererAttributes = Ext.apply({
74402                     segment: {
74403                         startAngle: slice.startAngle,
74404                         endAngle: slice.endAngle,
74405                         margin: 0,
74406                         rho: slice.rho,
74407                         startRho: rhoAcum + (deltaRho * donut / 100),
74408                         endRho: rhoAcum + deltaRho
74409                     },
74410                     hidden: (!slice.value && (slice.startAngle % 360) == (slice.endAngle % 360))
74411                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
74412                 item = Ext.apply({},
74413                 rendererAttributes.segment, {
74414                     slice: slice,
74415                     series: me,
74416                     storeItem: slice.storeItem,
74417                     index: i
74418                 });
74419                 me.calcMiddle(item);
74420                 if (enableShadows) {
74421                     item.shadows = slice.shadowAttrs[j];
74422                 }
74423                 items[i] = item;
74424                 // Create a new sprite if needed (no height)
74425                 if (!sprite) {
74426                     spriteOptions = Ext.apply({
74427                         type: "path",
74428                         group: group,
74429                         middle: item.middle
74430                     }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
74431                     sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
74432                 }
74433                 slice.sprite = slice.sprite || [];
74434                 item.sprite = sprite;
74435                 slice.sprite.push(sprite);
74436                 slice.point = [item.middle.x, item.middle.y];
74437                 if (animate) {
74438                     rendererAttributes = me.renderer(sprite, store.getAt(i), rendererAttributes, i, store);
74439                     sprite._to = rendererAttributes;
74440                     sprite._animating = true;
74441                     me.onAnimate(sprite, {
74442                         to: rendererAttributes,
74443                         listeners: {
74444                             afteranimate: {
74445                                 fn: function() {
74446                                     this._animating = false;
74447                                 },
74448                                 scope: sprite
74449                             }
74450                         }
74451                     });
74452                 } else {
74453                     rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(rendererAttributes, {
74454                         hidden: false
74455                     }), i, store);
74456                     sprite.setAttributes(rendererAttributes, true);
74457                 }
74458                 rhoAcum += deltaRho;
74459             }
74460         }
74461
74462         // Hide unused bars
74463         ln = group.getCount();
74464         for (i = 0; i < ln; i++) {
74465             if (!slices[(i / layers) >> 0] && group.getAt(i)) {
74466                 group.getAt(i).hide(true);
74467             }
74468         }
74469         if (enableShadows) {
74470             lnsh = shadowGroups.length;
74471             for (shindex = 0; shindex < ln; shindex++) {
74472                 if (!slices[(shindex / layers) >> 0]) {
74473                     for (j = 0; j < lnsh; j++) {
74474                         if (shadowGroups[j].getAt(shindex)) {
74475                             shadowGroups[j].getAt(shindex).hide(true);
74476                         }
74477                     }
74478                 }
74479             }
74480         }
74481         me.renderLabels();
74482         me.renderCallouts();
74483     },
74484
74485     // @private callback for when creating a label sprite.
74486     onCreateLabel: function(storeItem, item, i, display) {
74487         var me = this,
74488             group = me.labelsGroup,
74489             config = me.label,
74490             centerX = me.centerX,
74491             centerY = me.centerY,
74492             middle = item.middle,
74493             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {});
74494
74495         return me.chart.surface.add(Ext.apply({
74496             'type': 'text',
74497             'text-anchor': 'middle',
74498             'group': group,
74499             'x': middle.x,
74500             'y': middle.y
74501         }, endLabelStyle));
74502     },
74503
74504     // @private callback for when placing a label sprite.
74505     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
74506         var me = this,
74507             chart = me.chart,
74508             resizing = chart.resizing,
74509             config = me.label,
74510             format = config.renderer,
74511             field = [].concat(config.field),
74512             centerX = me.centerX,
74513             centerY = me.centerY,
74514             middle = item.middle,
74515             opt = {
74516                 x: middle.x,
74517                 y: middle.y
74518             },
74519             x = middle.x - centerX,
74520             y = middle.y - centerY,
74521             from = {},
74522             rho = 1,
74523             theta = Math.atan2(y, x || 1),
74524             dg = theta * 180 / Math.PI,
74525             prevDg;
74526         if (this.__excludes && this.__excludes[i]) {
74527             opt.hidden = true;
74528         }
74529         function fixAngle(a) {
74530             if (a < 0) {
74531                 a += 360;
74532             }
74533             return a % 360;
74534         }
74535
74536         label.setAttributes({
74537             text: format(storeItem.get(field[index]))
74538         }, true);
74539
74540         switch (display) {
74541         case 'outside':
74542             rho = Math.sqrt(x * x + y * y) * 2;
74543             //update positions
74544             opt.x = rho * Math.cos(theta) + centerX;
74545             opt.y = rho * Math.sin(theta) + centerY;
74546             break;
74547
74548         case 'rotate':
74549             dg = fixAngle(dg);
74550             dg = (dg > 90 && dg < 270) ? dg + 180: dg;
74551
74552             prevDg = label.attr.rotation.degrees;
74553             if (prevDg != null && Math.abs(prevDg - dg) > 180) {
74554                 if (dg > prevDg) {
74555                     dg -= 360;
74556                 } else {
74557                     dg += 360;
74558                 }
74559                 dg = dg % 360;
74560             } else {
74561                 dg = fixAngle(dg);
74562             }
74563             //update rotation angle
74564             opt.rotate = {
74565                 degrees: dg,
74566                 x: opt.x,
74567                 y: opt.y
74568             };
74569             break;
74570
74571         default:
74572             break;
74573         }
74574         //ensure the object has zero translation
74575         opt.translate = {
74576             x: 0, y: 0
74577         };
74578         if (animate && !resizing && (display != 'rotate' || prevDg != null)) {
74579             me.onAnimate(label, {
74580                 to: opt
74581             });
74582         } else {
74583             label.setAttributes(opt, true);
74584         }
74585         label._from = from;
74586     },
74587
74588     // @private callback for when placing a callout sprite.
74589     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
74590         var me = this,
74591             chart = me.chart,
74592             resizing = chart.resizing,
74593             config = me.callouts,
74594             centerX = me.centerX,
74595             centerY = me.centerY,
74596             middle = item.middle,
74597             opt = {
74598                 x: middle.x,
74599                 y: middle.y
74600             },
74601             x = middle.x - centerX,
74602             y = middle.y - centerY,
74603             rho = 1,
74604             rhoCenter,
74605             theta = Math.atan2(y, x || 1),
74606             bbox = callout.label.getBBox(),
74607             offsetFromViz = 20,
74608             offsetToSide = 10,
74609             offsetBox = 10,
74610             p;
74611
74612         //should be able to config this.
74613         rho = item.endRho + offsetFromViz;
74614         rhoCenter = (item.endRho + item.startRho) / 2 + (item.endRho - item.startRho) / 3;
74615         //update positions
74616         opt.x = rho * Math.cos(theta) + centerX;
74617         opt.y = rho * Math.sin(theta) + centerY;
74618
74619         x = rhoCenter * Math.cos(theta);
74620         y = rhoCenter * Math.sin(theta);
74621
74622         if (chart.animate) {
74623             //set the line from the middle of the pie to the box.
74624             me.onAnimate(callout.lines, {
74625                 to: {
74626                     path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
74627                 }
74628             });
74629             //set box position
74630             me.onAnimate(callout.box, {
74631                 to: {
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             });
74638             //set text position
74639             me.onAnimate(callout.label, {
74640                 to: {
74641                     x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
74642                     y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
74643                 }
74644             });
74645         } else {
74646             //set the line from the middle of the pie to the box.
74647             callout.lines.setAttributes({
74648                 path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
74649             },
74650             true);
74651             //set box position
74652             callout.box.setAttributes({
74653                 x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
74654                 y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
74655                 width: bbox.width + 2 * offsetBox,
74656                 height: bbox.height + 2 * offsetBox
74657             },
74658             true);
74659             //set text position
74660             callout.label.setAttributes({
74661                 x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
74662                 y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
74663             },
74664             true);
74665         }
74666         for (p in callout) {
74667             callout[p].show(true);
74668         }
74669     },
74670
74671     // @private handles sprite animation for the series.
74672     onAnimate: function(sprite, attr) {
74673         sprite.show();
74674         return this.callParent(arguments);
74675     },
74676
74677     isItemInPoint: function(x, y, item, i) {
74678         var me = this,
74679             cx = me.centerX,
74680             cy = me.centerY,
74681             abs = Math.abs,
74682             dx = abs(x - cx),
74683             dy = abs(y - cy),
74684             startAngle = item.startAngle,
74685             endAngle = item.endAngle,
74686             rho = Math.sqrt(dx * dx + dy * dy),
74687             angle = Math.atan2(y - cy, x - cx) / me.rad;
74688
74689         // normalize to the same range of angles created by drawSeries
74690         if (angle > me.firstAngle) {
74691             angle -= 360;
74692         }
74693         return (angle <= startAngle && angle > endAngle
74694                 && rho >= item.startRho && rho <= item.endRho);
74695     },
74696
74697     // @private hides all elements in the series.
74698     hideAll: function() {
74699         var i, l, shadow, shadows, sh, lsh, sprite;
74700         if (!isNaN(this._index)) {
74701             this.__excludes = this.__excludes || [];
74702             this.__excludes[this._index] = true;
74703             sprite = this.slices[this._index].sprite;
74704             for (sh = 0, lsh = sprite.length; sh < lsh; sh++) {
74705                 sprite[sh].setAttributes({
74706                     hidden: true
74707                 }, true);
74708             }
74709             if (this.slices[this._index].shadowAttrs) {
74710                 for (i = 0, shadows = this.slices[this._index].shadowAttrs, l = shadows.length; i < l; i++) {
74711                     shadow = shadows[i];
74712                     for (sh = 0, lsh = shadow.length; sh < lsh; sh++) {
74713                         shadow[sh].setAttributes({
74714                             hidden: true
74715                         }, true);
74716                     }
74717                 }
74718             }
74719             this.drawSeries();
74720         }
74721     },
74722
74723     // @private shows all elements in the series.
74724     showAll: function() {
74725         if (!isNaN(this._index)) {
74726             this.__excludes[this._index] = false;
74727             this.drawSeries();
74728         }
74729     },
74730
74731     /**
74732      * Highlight the specified item. If no item is provided the whole series will be highlighted.
74733      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74734      */
74735     highlightItem: function(item) {
74736         var me = this,
74737             rad = me.rad;
74738         item = item || this.items[this._index];
74739
74740         //TODO(nico): sometimes in IE itemmouseover is triggered
74741         //twice without triggering itemmouseout in between. This
74742         //fixes the highlighting bug. Eventually, events should be
74743         //changed to trigger one itemmouseout between two itemmouseovers.
74744         this.unHighlightItem();
74745
74746         if (!item || item.sprite && item.sprite._animating) {
74747             return;
74748         }
74749         me.callParent([item]);
74750         if (!me.highlight) {
74751             return;
74752         }
74753         if ('segment' in me.highlightCfg) {
74754             var highlightSegment = me.highlightCfg.segment,
74755                 animate = me.chart.animate,
74756                 attrs, i, shadows, shadow, ln, to, itemHighlightSegment, prop;
74757             //animate labels
74758             if (me.labelsGroup) {
74759                 var group = me.labelsGroup,
74760                     display = me.label.display,
74761                     label = group.getAt(item.index),
74762                     middle = (item.startAngle + item.endAngle) / 2 * rad,
74763                     r = highlightSegment.margin || 0,
74764                     x = r * Math.cos(middle),
74765                     y = r * Math.sin(middle);
74766
74767                 //TODO(nico): rounding to 1e-10
74768                 //gives the right translation. Translation
74769                 //was buggy for very small numbers. In this
74770                 //case we're not looking to translate to very small
74771                 //numbers but not to translate at all.
74772                 if (Math.abs(x) < 1e-10) {
74773                     x = 0;
74774                 }
74775                 if (Math.abs(y) < 1e-10) {
74776                     y = 0;
74777                 }
74778
74779                 if (animate) {
74780                     label.stopAnimation();
74781                     label.animate({
74782                         to: {
74783                             translate: {
74784                                 x: x,
74785                                 y: y
74786                             }
74787                         },
74788                         duration: me.highlightDuration
74789                     });
74790                 }
74791                 else {
74792                     label.setAttributes({
74793                         translate: {
74794                             x: x,
74795                             y: y
74796                         }
74797                     }, true);
74798                 }
74799             }
74800             //animate shadows
74801             if (me.chart.shadow && item.shadows) {
74802                 i = 0;
74803                 shadows = item.shadows;
74804                 ln = shadows.length;
74805                 for (; i < ln; i++) {
74806                     shadow = shadows[i];
74807                     to = {};
74808                     itemHighlightSegment = item.sprite._from.segment;
74809                     for (prop in itemHighlightSegment) {
74810                         if (! (prop in highlightSegment)) {
74811                             to[prop] = itemHighlightSegment[prop];
74812                         }
74813                     }
74814                     attrs = {
74815                         segment: Ext.applyIf(to, me.highlightCfg.segment)
74816                     };
74817                     if (animate) {
74818                         shadow.stopAnimation();
74819                         shadow.animate({
74820                             to: attrs,
74821                             duration: me.highlightDuration
74822                         });
74823                     }
74824                     else {
74825                         shadow.setAttributes(attrs, true);
74826                     }
74827                 }
74828             }
74829         }
74830     },
74831
74832     /**
74833      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
74834      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74835      */
74836     unHighlightItem: function() {
74837         var me = this;
74838         if (!me.highlight) {
74839             return;
74840         }
74841
74842         if (('segment' in me.highlightCfg) && me.items) {
74843             var items = me.items,
74844                 animate = me.chart.animate,
74845                 shadowsEnabled = !!me.chart.shadow,
74846                 group = me.labelsGroup,
74847                 len = items.length,
74848                 i = 0,
74849                 j = 0,
74850                 display = me.label.display,
74851                 shadowLen, p, to, ihs, hs, sprite, shadows, shadow, item, label, attrs;
74852
74853             for (; i < len; i++) {
74854                 item = items[i];
74855                 if (!item) {
74856                     continue;
74857                 }
74858                 sprite = item.sprite;
74859                 if (sprite && sprite._highlighted) {
74860                     //animate labels
74861                     if (group) {
74862                         label = group.getAt(item.index);
74863                         attrs = Ext.apply({
74864                             translate: {
74865                                 x: 0,
74866                                 y: 0
74867                             }
74868                         },
74869                         display == 'rotate' ? {
74870                             rotate: {
74871                                 x: label.attr.x,
74872                                 y: label.attr.y,
74873                                 degrees: label.attr.rotation.degrees
74874                             }
74875                         }: {});
74876                         if (animate) {
74877                             label.stopAnimation();
74878                             label.animate({
74879                                 to: attrs,
74880                                 duration: me.highlightDuration
74881                             });
74882                         }
74883                         else {
74884                             label.setAttributes(attrs, true);
74885                         }
74886                     }
74887                     if (shadowsEnabled) {
74888                         shadows = item.shadows;
74889                         shadowLen = shadows.length;
74890                         for (; j < shadowLen; j++) {
74891                             to = {};
74892                             ihs = item.sprite._to.segment;
74893                             hs = item.sprite._from.segment;
74894                             Ext.apply(to, hs);
74895                             for (p in ihs) {
74896                                 if (! (p in hs)) {
74897                                     to[p] = ihs[p];
74898                                 }
74899                             }
74900                             shadow = shadows[j];
74901                             if (animate) {
74902                                 shadow.stopAnimation();
74903                                 shadow.animate({
74904                                     to: {
74905                                         segment: to
74906                                     },
74907                                     duration: me.highlightDuration
74908                                 });
74909                             }
74910                             else {
74911                                 shadow.setAttributes({ segment: to }, true);
74912                             }
74913                         }
74914                     }
74915                 }
74916             }
74917         }
74918         me.callParent(arguments);
74919     },
74920
74921     /**
74922      * Returns the color of the series (to be displayed as color for the series legend item).
74923      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74924      */
74925     getLegendColor: function(index) {
74926         var me = this;
74927         return (me.colorSet && me.colorSet[index % me.colorSet.length]) || me.colorArrayStyle[index % me.colorArrayStyle.length];
74928     }
74929 });
74930
74931
74932 /**
74933  * @class Ext.chart.series.Radar
74934  * @extends Ext.chart.series.Series
74935  *
74936  * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for
74937  * a constrained number of categories.
74938  *
74939  * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart
74940  * documentation for more information. A typical configuration object for the radar series could be:
74941  *
74942  *     @example
74943  *     var store = Ext.create('Ext.data.JsonStore', {
74944  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
74945  *         data: [
74946  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
74947  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
74948  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
74949  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
74950  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
74951  *         ]
74952  *     });
74953  *
74954  *     Ext.create('Ext.chart.Chart', {
74955  *         renderTo: Ext.getBody(),
74956  *         width: 500,
74957  *         height: 300,
74958  *         animate: true,
74959  *         theme:'Category2',
74960  *         store: store,
74961  *         axes: [{
74962  *             type: 'Radial',
74963  *             position: 'radial',
74964  *             label: {
74965  *                 display: true
74966  *             }
74967  *         }],
74968  *         series: [{
74969  *             type: 'radar',
74970  *             xField: 'name',
74971  *             yField: 'data3',
74972  *             showInLegend: true,
74973  *             showMarkers: true,
74974  *             markerConfig: {
74975  *                 radius: 5,
74976  *                 size: 5
74977  *             },
74978  *             style: {
74979  *                 'stroke-width': 2,
74980  *                 fill: 'none'
74981  *             }
74982  *         },{
74983  *             type: 'radar',
74984  *             xField: 'name',
74985  *             yField: 'data2',
74986  *             showMarkers: true,
74987  *             showInLegend: true,
74988  *             markerConfig: {
74989  *                 radius: 5,
74990  *                 size: 5
74991  *             },
74992  *             style: {
74993  *                 'stroke-width': 2,
74994  *                 fill: 'none'
74995  *             }
74996  *         },{
74997  *             type: 'radar',
74998  *             xField: 'name',
74999  *             yField: 'data5',
75000  *             showMarkers: true,
75001  *             showInLegend: true,
75002  *             markerConfig: {
75003  *                 radius: 5,
75004  *                 size: 5
75005  *             },
75006  *             style: {
75007  *                 'stroke-width': 2,
75008  *                 fill: 'none'
75009  *             }
75010  *         }]
75011  *     });
75012  *
75013  * In this configuration we add three series to the chart. Each of these series is bound to the same
75014  * categories field, `name` but bound to different properties for each category, `data1`, `data2` and
75015  * `data3` respectively. All series display markers by having `showMarkers` enabled. The configuration
75016  * for the markers of each series can be set by adding properties onto the markerConfig object.
75017  * Finally we override some theme styling properties by adding properties to the `style` object.
75018  *
75019  * @xtype radar
75020  */
75021 Ext.define('Ext.chart.series.Radar', {
75022
75023     /* Begin Definitions */
75024
75025     extend: 'Ext.chart.series.Series',
75026
75027     requires: ['Ext.chart.Shape', 'Ext.fx.Anim'],
75028
75029     /* End Definitions */
75030
75031     type: "radar",
75032     alias: 'series.radar',
75033
75034
75035     rad: Math.PI / 180,
75036
75037     showInLegend: false,
75038
75039     /**
75040      * @cfg {Object} style
75041      * An object containing styles for overriding series styles from Theming.
75042      */
75043     style: {},
75044
75045     constructor: function(config) {
75046         this.callParent(arguments);
75047         var me = this,
75048             surface = me.chart.surface, i, l;
75049         me.group = surface.getGroup(me.seriesId);
75050         if (me.showMarkers) {
75051             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
75052         }
75053     },
75054
75055     /**
75056      * Draws the series for the current chart.
75057      */
75058     drawSeries: function() {
75059         var me = this,
75060             store = me.chart.getChartStore(),
75061             group = me.group,
75062             sprite,
75063             chart = me.chart,
75064             animate = chart.animate,
75065             field = me.field || me.yField,
75066             surface = chart.surface,
75067             chartBBox = chart.chartBBox,
75068             rendererAttributes,
75069             centerX, centerY,
75070             items,
75071             radius,
75072             maxValue = 0,
75073             fields = [],
75074             max = Math.max,
75075             cos = Math.cos,
75076             sin = Math.sin,
75077             pi2 = Math.PI * 2,
75078             l = store.getCount(),
75079             startPath, path, x, y, rho,
75080             i, nfields,
75081             seriesStyle = me.seriesStyle,
75082             seriesLabelStyle = me.seriesLabelStyle,
75083             first = chart.resizing || !me.radar,
75084             axis = chart.axes && chart.axes.get(0),
75085             aggregate = !(axis && axis.maximum);
75086
75087         me.setBBox();
75088
75089         maxValue = aggregate? 0 : (axis.maximum || 0);
75090
75091         Ext.apply(seriesStyle, me.style || {});
75092
75093         //if the store is empty then there's nothing to draw
75094         if (!store || !store.getCount()) {
75095             return;
75096         }
75097
75098         me.unHighlightItem();
75099         me.cleanHighlights();
75100
75101         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
75102         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
75103         me.radius = radius = Math.min(chartBBox.width, chartBBox.height) /2;
75104         me.items = items = [];
75105
75106         if (aggregate) {
75107             //get all renderer fields
75108             chart.series.each(function(series) {
75109                 fields.push(series.yField);
75110             });
75111             //get maxValue to interpolate
75112             store.each(function(record, i) {
75113                 for (i = 0, nfields = fields.length; i < nfields; i++) {
75114                     maxValue = max(+record.get(fields[i]), maxValue);
75115                 }
75116             });
75117         }
75118         //ensure non-zero value.
75119         maxValue = maxValue || 1;
75120         //create path and items
75121         startPath = []; path = [];
75122         store.each(function(record, i) {
75123             rho = radius * record.get(field) / maxValue;
75124             x = rho * cos(i / l * pi2);
75125             y = rho * sin(i / l * pi2);
75126             if (i == 0) {
75127                 path.push('M', x + centerX, y + centerY);
75128                 startPath.push('M', 0.01 * x + centerX, 0.01 * y + centerY);
75129             } else {
75130                 path.push('L', x + centerX, y + centerY);
75131                 startPath.push('L', 0.01 * x + centerX, 0.01 * y + centerY);
75132             }
75133             items.push({
75134                 sprite: false, //TODO(nico): add markers
75135                 point: [centerX + x, centerY + y],
75136                 series: me
75137             });
75138         });
75139         path.push('Z');
75140         //create path sprite
75141         if (!me.radar) {
75142             me.radar = surface.add(Ext.apply({
75143                 type: 'path',
75144                 group: group,
75145                 path: startPath
75146             }, seriesStyle || {}));
75147         }
75148         //reset on resizing
75149         if (chart.resizing) {
75150             me.radar.setAttributes({
75151                 path: startPath
75152             }, true);
75153         }
75154         //render/animate
75155         if (chart.animate) {
75156             me.onAnimate(me.radar, {
75157                 to: Ext.apply({
75158                     path: path
75159                 }, seriesStyle || {})
75160             });
75161         } else {
75162             me.radar.setAttributes(Ext.apply({
75163                 path: path
75164             }, seriesStyle || {}), true);
75165         }
75166         //render markers, labels and callouts
75167         if (me.showMarkers) {
75168             me.drawMarkers();
75169         }
75170         me.renderLabels();
75171         me.renderCallouts();
75172     },
75173
75174     // @private draws the markers for the lines (if any).
75175     drawMarkers: function() {
75176         var me = this,
75177             chart = me.chart,
75178             surface = chart.surface,
75179             markerStyle = Ext.apply({}, me.markerStyle || {}),
75180             endMarkerStyle = Ext.apply(markerStyle, me.markerConfig),
75181             items = me.items,
75182             type = endMarkerStyle.type,
75183             markerGroup = me.markerGroup,
75184             centerX = me.centerX,
75185             centerY = me.centerY,
75186             item, i, l, marker;
75187
75188         delete endMarkerStyle.type;
75189
75190         for (i = 0, l = items.length; i < l; i++) {
75191             item = items[i];
75192             marker = markerGroup.getAt(i);
75193             if (!marker) {
75194                 marker = Ext.chart.Shape[type](surface, Ext.apply({
75195                     group: markerGroup,
75196                     x: 0,
75197                     y: 0,
75198                     translate: {
75199                         x: centerX,
75200                         y: centerY
75201                     }
75202                 }, endMarkerStyle));
75203             }
75204             else {
75205                 marker.show();
75206             }
75207             if (chart.resizing) {
75208                 marker.setAttributes({
75209                     x: 0,
75210                     y: 0,
75211                     translate: {
75212                         x: centerX,
75213                         y: centerY
75214                     }
75215                 }, true);
75216             }
75217             marker._to = {
75218                 translate: {
75219                     x: item.point[0],
75220                     y: item.point[1]
75221                 }
75222             };
75223             //render/animate
75224             if (chart.animate) {
75225                 me.onAnimate(marker, {
75226                     to: marker._to
75227                 });
75228             }
75229             else {
75230                 marker.setAttributes(Ext.apply(marker._to, endMarkerStyle || {}), true);
75231             }
75232         }
75233     },
75234
75235     isItemInPoint: function(x, y, item) {
75236         var point,
75237             tolerance = 10,
75238             abs = Math.abs;
75239         point = item.point;
75240         return (abs(point[0] - x) <= tolerance &&
75241                 abs(point[1] - y) <= tolerance);
75242     },
75243
75244     // @private callback for when creating a label sprite.
75245     onCreateLabel: function(storeItem, item, i, display) {
75246         var me = this,
75247             group = me.labelsGroup,
75248             config = me.label,
75249             centerX = me.centerX,
75250             centerY = me.centerY,
75251             point = item.point,
75252             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config);
75253
75254         return me.chart.surface.add(Ext.apply({
75255             'type': 'text',
75256             'text-anchor': 'middle',
75257             'group': group,
75258             'x': centerX,
75259             'y': centerY
75260         }, config || {}));
75261     },
75262
75263     // @private callback for when placing a label sprite.
75264     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
75265         var me = this,
75266             chart = me.chart,
75267             resizing = chart.resizing,
75268             config = me.label,
75269             format = config.renderer,
75270             field = config.field,
75271             centerX = me.centerX,
75272             centerY = me.centerY,
75273             opt = {
75274                 x: item.point[0],
75275                 y: item.point[1]
75276             },
75277             x = opt.x - centerX,
75278             y = opt.y - centerY;
75279
75280         label.setAttributes({
75281             text: format(storeItem.get(field)),
75282             hidden: true
75283         },
75284         true);
75285
75286         if (resizing) {
75287             label.setAttributes({
75288                 x: centerX,
75289                 y: centerY
75290             }, true);
75291         }
75292
75293         if (animate) {
75294             label.show(true);
75295             me.onAnimate(label, {
75296                 to: opt
75297             });
75298         } else {
75299             label.setAttributes(opt, true);
75300             label.show(true);
75301         }
75302     },
75303
75304     // @private for toggling (show/hide) series.
75305     toggleAll: function(show) {
75306         var me = this,
75307             i, ln, shadow, shadows;
75308         if (!show) {
75309             Ext.chart.series.Radar.superclass.hideAll.call(me);
75310         }
75311         else {
75312             Ext.chart.series.Radar.superclass.showAll.call(me);
75313         }
75314         if (me.radar) {
75315             me.radar.setAttributes({
75316                 hidden: !show
75317             }, true);
75318             //hide shadows too
75319             if (me.radar.shadows) {
75320                 for (i = 0, shadows = me.radar.shadows, ln = shadows.length; i < ln; i++) {
75321                     shadow = shadows[i];
75322                     shadow.setAttributes({
75323                         hidden: !show
75324                     }, true);
75325                 }
75326             }
75327         }
75328     },
75329
75330     // @private hide all elements in the series.
75331     hideAll: function() {
75332         this.toggleAll(false);
75333         this.hideMarkers(0);
75334     },
75335
75336     // @private show all elements in the series.
75337     showAll: function() {
75338         this.toggleAll(true);
75339     },
75340
75341     // @private hide all markers that belong to `markerGroup`
75342     hideMarkers: function(index) {
75343         var me = this,
75344             count = me.markerGroup && me.markerGroup.getCount() || 0,
75345             i = index || 0;
75346         for (; i < count; i++) {
75347             me.markerGroup.getAt(i).hide(true);
75348         }
75349     }
75350 });
75351
75352
75353 /**
75354  * @class Ext.chart.series.Scatter
75355  * @extends Ext.chart.series.Cartesian
75356  *
75357  * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
75358  * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
75359  * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
75360  * documentation for more information on creating charts. A typical configuration object for the scatter could be:
75361  *
75362  *     @example
75363  *     var store = Ext.create('Ext.data.JsonStore', {
75364  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
75365  *         data: [
75366  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
75367  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
75368  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
75369  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
75370  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
75371  *         ]
75372  *     });
75373  *
75374  *     Ext.create('Ext.chart.Chart', {
75375  *         renderTo: Ext.getBody(),
75376  *         width: 500,
75377  *         height: 300,
75378  *         animate: true,
75379  *         theme:'Category2',
75380  *         store: store,
75381  *         axes: [{
75382  *             type: 'Numeric',
75383  *             position: 'left',
75384  *             fields: ['data2', 'data3'],
75385  *             title: 'Sample Values',
75386  *             grid: true,
75387  *             minimum: 0
75388  *         }, {
75389  *             type: 'Category',
75390  *             position: 'bottom',
75391  *             fields: ['name'],
75392  *             title: 'Sample Metrics'
75393  *         }],
75394  *         series: [{
75395  *             type: 'scatter',
75396  *             markerConfig: {
75397  *                 radius: 5,
75398  *                 size: 5
75399  *             },
75400  *             axis: 'left',
75401  *             xField: 'name',
75402  *             yField: 'data2'
75403  *         }, {
75404  *             type: 'scatter',
75405  *             markerConfig: {
75406  *                 radius: 5,
75407  *                 size: 5
75408  *             },
75409  *             axis: 'left',
75410  *             xField: 'name',
75411  *             yField: 'data3'
75412  *         }]
75413  *     });
75414  *
75415  * 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,
75416  * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
75417  * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as
75418  * axis to show the current values of the elements.
75419  *
75420  * @xtype scatter
75421  */
75422 Ext.define('Ext.chart.series.Scatter', {
75423
75424     /* Begin Definitions */
75425
75426     extend: 'Ext.chart.series.Cartesian',
75427
75428     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
75429
75430     /* End Definitions */
75431
75432     type: 'scatter',
75433     alias: 'series.scatter',
75434
75435     /**
75436      * @cfg {Object} markerConfig
75437      * The display style for the scatter series markers.
75438      */
75439
75440     /**
75441      * @cfg {Object} style
75442      * Append styling properties to this object for it to override theme properties.
75443      */
75444     
75445     /**
75446      * @cfg {String/Array} axis
75447      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
75448      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
75449      * relative scale will be used. If multiple axes are being used, they should both be specified in in the configuration.
75450      */
75451
75452     constructor: function(config) {
75453         this.callParent(arguments);
75454         var me = this,
75455             shadow = me.chart.shadow,
75456             surface = me.chart.surface, i, l;
75457         Ext.apply(me, config, {
75458             style: {},
75459             markerConfig: {},
75460             shadowAttributes: [{
75461                 "stroke-width": 6,
75462                 "stroke-opacity": 0.05,
75463                 stroke: 'rgb(0, 0, 0)'
75464             }, {
75465                 "stroke-width": 4,
75466                 "stroke-opacity": 0.1,
75467                 stroke: 'rgb(0, 0, 0)'
75468             }, {
75469                 "stroke-width": 2,
75470                 "stroke-opacity": 0.15,
75471                 stroke: 'rgb(0, 0, 0)'
75472             }]
75473         });
75474         me.group = surface.getGroup(me.seriesId);
75475         if (shadow) {
75476             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
75477                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
75478             }
75479         }
75480     },
75481
75482     // @private Get chart and data boundaries
75483     getBounds: function() {
75484         var me = this,
75485             chart = me.chart,
75486             store = chart.getChartStore(),
75487             axes = [].concat(me.axis),
75488             bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
75489
75490         me.setBBox();
75491         bbox = me.bbox;
75492
75493         for (i = 0, ln = axes.length; i < ln; i++) {
75494             axis = chart.axes.get(axes[i]);
75495             if (axis) {
75496                 ends = axis.calcEnds();
75497                 if (axis.position == 'top' || axis.position == 'bottom') {
75498                     minX = ends.from;
75499                     maxX = ends.to;
75500                 }
75501                 else {
75502                     minY = ends.from;
75503                     maxY = ends.to;
75504                 }
75505             }
75506         }
75507         // If a field was specified without a corresponding axis, create one to get bounds
75508         if (me.xField && !Ext.isNumber(minX)) {
75509             axis = Ext.create('Ext.chart.axis.Axis', {
75510                 chart: chart,
75511                 fields: [].concat(me.xField)
75512             }).calcEnds();
75513             minX = axis.from;
75514             maxX = axis.to;
75515         }
75516         if (me.yField && !Ext.isNumber(minY)) {
75517             axis = Ext.create('Ext.chart.axis.Axis', {
75518                 chart: chart,
75519                 fields: [].concat(me.yField)
75520             }).calcEnds();
75521             minY = axis.from;
75522             maxY = axis.to;
75523         }
75524
75525         if (isNaN(minX)) {
75526             minX = 0;
75527             maxX = store.getCount() - 1;
75528             xScale = bbox.width / (store.getCount() - 1);
75529         }
75530         else {
75531             xScale = bbox.width / (maxX - minX);
75532         }
75533
75534         if (isNaN(minY)) {
75535             minY = 0;
75536             maxY = store.getCount() - 1;
75537             yScale = bbox.height / (store.getCount() - 1);
75538         }
75539         else {
75540             yScale = bbox.height / (maxY - minY);
75541         }
75542
75543         return {
75544             bbox: bbox,
75545             minX: minX,
75546             minY: minY,
75547             xScale: xScale,
75548             yScale: yScale
75549         };
75550     },
75551
75552     // @private Build an array of paths for the chart
75553     getPaths: function() {
75554         var me = this,
75555             chart = me.chart,
75556             enableShadows = chart.shadow,
75557             store = chart.getChartStore(),
75558             group = me.group,
75559             bounds = me.bounds = me.getBounds(),
75560             bbox = me.bbox,
75561             xScale = bounds.xScale,
75562             yScale = bounds.yScale,
75563             minX = bounds.minX,
75564             minY = bounds.minY,
75565             boxX = bbox.x,
75566             boxY = bbox.y,
75567             boxHeight = bbox.height,
75568             items = me.items = [],
75569             attrs = [],
75570             x, y, xValue, yValue, sprite;
75571
75572         store.each(function(record, i) {
75573             xValue = record.get(me.xField);
75574             yValue = record.get(me.yField);
75575             //skip undefined values
75576             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
75577                 if (Ext.isDefined(Ext.global.console)) {
75578                     Ext.global.console.warn("[Ext.chart.series.Scatter]  Skipping a store element with an undefined value at ", record, xValue, yValue);
75579                 }
75580                 return;
75581             }
75582             // Ensure a value
75583             if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)) {
75584                 xValue = i;
75585             }
75586             if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {
75587                 yValue = i;
75588             }
75589             x = boxX + (xValue - minX) * xScale;
75590             y = boxY + boxHeight - (yValue - minY) * yScale;
75591             attrs.push({
75592                 x: x,
75593                 y: y
75594             });
75595
75596             me.items.push({
75597                 series: me,
75598                 value: [xValue, yValue],
75599                 point: [x, y],
75600                 storeItem: record
75601             });
75602
75603             // When resizing, reset before animating
75604             if (chart.animate && chart.resizing) {
75605                 sprite = group.getAt(i);
75606                 if (sprite) {
75607                     me.resetPoint(sprite);
75608                     if (enableShadows) {
75609                         me.resetShadow(sprite);
75610                     }
75611                 }
75612             }
75613         });
75614         return attrs;
75615     },
75616
75617     // @private translate point to the center
75618     resetPoint: function(sprite) {
75619         var bbox = this.bbox;
75620         sprite.setAttributes({
75621             translate: {
75622                 x: (bbox.x + bbox.width) / 2,
75623                 y: (bbox.y + bbox.height) / 2
75624             }
75625         }, true);
75626     },
75627
75628     // @private translate shadows of a sprite to the center
75629     resetShadow: function(sprite) {
75630         var me = this,
75631             shadows = sprite.shadows,
75632             shadowAttributes = me.shadowAttributes,
75633             ln = me.shadowGroups.length,
75634             bbox = me.bbox,
75635             i, attr;
75636         for (i = 0; i < ln; i++) {
75637             attr = Ext.apply({}, shadowAttributes[i]);
75638             if (attr.translate) {
75639                 attr.translate.x += (bbox.x + bbox.width) / 2;
75640                 attr.translate.y += (bbox.y + bbox.height) / 2;
75641             }
75642             else {
75643                 attr.translate = {
75644                     x: (bbox.x + bbox.width) / 2,
75645                     y: (bbox.y + bbox.height) / 2
75646                 };
75647             }
75648             shadows[i].setAttributes(attr, true);
75649         }
75650     },
75651
75652     // @private create a new point
75653     createPoint: function(attr, type) {
75654         var me = this,
75655             chart = me.chart,
75656             group = me.group,
75657             bbox = me.bbox;
75658
75659         return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
75660             x: 0,
75661             y: 0,
75662             group: group,
75663             translate: {
75664                 x: (bbox.x + bbox.width) / 2,
75665                 y: (bbox.y + bbox.height) / 2
75666             }
75667         }, attr));
75668     },
75669
75670     // @private create a new set of shadows for a sprite
75671     createShadow: function(sprite, endMarkerStyle, type) {
75672         var me = this,
75673             chart = me.chart,
75674             shadowGroups = me.shadowGroups,
75675             shadowAttributes = me.shadowAttributes,
75676             lnsh = shadowGroups.length,
75677             bbox = me.bbox,
75678             i, shadow, shadows, attr;
75679
75680         sprite.shadows = shadows = [];
75681
75682         for (i = 0; i < lnsh; i++) {
75683             attr = Ext.apply({}, shadowAttributes[i]);
75684             if (attr.translate) {
75685                 attr.translate.x += (bbox.x + bbox.width) / 2;
75686                 attr.translate.y += (bbox.y + bbox.height) / 2;
75687             }
75688             else {
75689                 Ext.apply(attr, {
75690                     translate: {
75691                         x: (bbox.x + bbox.width) / 2,
75692                         y: (bbox.y + bbox.height) / 2
75693                     }
75694                 });
75695             }
75696             Ext.apply(attr, endMarkerStyle);
75697             shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
75698                 x: 0,
75699                 y: 0,
75700                 group: shadowGroups[i]
75701             }, attr));
75702             shadows.push(shadow);
75703         }
75704     },
75705
75706     /**
75707      * Draws the series for the current chart.
75708      */
75709     drawSeries: function() {
75710         var me = this,
75711             chart = me.chart,
75712             store = chart.getChartStore(),
75713             group = me.group,
75714             enableShadows = chart.shadow,
75715             shadowGroups = me.shadowGroups,
75716             shadowAttributes = me.shadowAttributes,
75717             lnsh = shadowGroups.length,
75718             sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
75719             rendererAttributes, shadowAttribute;
75720
75721         endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
75722         type = endMarkerStyle.type;
75723         delete endMarkerStyle.type;
75724
75725         //if the store is empty then there's nothing to be rendered
75726         if (!store || !store.getCount()) {
75727             return;
75728         }
75729
75730         me.unHighlightItem();
75731         me.cleanHighlights();
75732
75733         attrs = me.getPaths();
75734         ln = attrs.length;
75735         for (i = 0; i < ln; i++) {
75736             attr = attrs[i];
75737             sprite = group.getAt(i);
75738             Ext.apply(attr, endMarkerStyle);
75739
75740             // Create a new sprite if needed (no height)
75741             if (!sprite) {
75742                 sprite = me.createPoint(attr, type);
75743                 if (enableShadows) {
75744                     me.createShadow(sprite, endMarkerStyle, type);
75745                 }
75746             }
75747
75748             shadows = sprite.shadows;
75749             if (chart.animate) {
75750                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
75751                 sprite._to = rendererAttributes;
75752                 me.onAnimate(sprite, {
75753                     to: rendererAttributes
75754                 });
75755                 //animate shadows
75756                 for (shindex = 0; shindex < lnsh; shindex++) {
75757                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
75758                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
75759                         hidden: false,
75760                         translate: {
75761                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
75762                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
75763                         }
75764                     }, shadowAttribute), i, store);
75765                     me.onAnimate(shadows[shindex], { to: rendererAttributes });
75766                 }
75767             }
75768             else {
75769                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
75770                 sprite._to = rendererAttributes;
75771                 sprite.setAttributes(rendererAttributes, true);
75772                 //animate shadows
75773                 for (shindex = 0; shindex < lnsh; shindex++) {
75774                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
75775                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
75776                         hidden: false,
75777                         translate: {
75778                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
75779                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
75780                         } 
75781                     }, shadowAttribute), i, store);
75782                     shadows[shindex].setAttributes(rendererAttributes, true);
75783                 }
75784             }
75785             me.items[i].sprite = sprite;
75786         }
75787
75788         // Hide unused sprites
75789         ln = group.getCount();
75790         for (i = attrs.length; i < ln; i++) {
75791             group.getAt(i).hide(true);
75792         }
75793         me.renderLabels();
75794         me.renderCallouts();
75795     },
75796
75797     // @private callback for when creating a label sprite.
75798     onCreateLabel: function(storeItem, item, i, display) {
75799         var me = this,
75800             group = me.labelsGroup,
75801             config = me.label,
75802             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
75803             bbox = me.bbox;
75804
75805         return me.chart.surface.add(Ext.apply({
75806             type: 'text',
75807             group: group,
75808             x: item.point[0],
75809             y: bbox.y + bbox.height / 2
75810         }, endLabelStyle));
75811     },
75812
75813     // @private callback for when placing a label sprite.
75814     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
75815         var me = this,
75816             chart = me.chart,
75817             resizing = chart.resizing,
75818             config = me.label,
75819             format = config.renderer,
75820             field = config.field,
75821             bbox = me.bbox,
75822             x = item.point[0],
75823             y = item.point[1],
75824             radius = item.sprite.attr.radius,
75825             bb, width, height, anim;
75826
75827         label.setAttributes({
75828             text: format(storeItem.get(field)),
75829             hidden: true
75830         }, true);
75831
75832         if (display == 'rotate') {
75833             label.setAttributes({
75834                 'text-anchor': 'start',
75835                 'rotation': {
75836                     x: x,
75837                     y: y,
75838                     degrees: -45
75839                 }
75840             }, true);
75841             //correct label position to fit into the box
75842             bb = label.getBBox();
75843             width = bb.width;
75844             height = bb.height;
75845             x = x < bbox.x? bbox.x : x;
75846             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
75847             y = (y - height < bbox.y)? bbox.y + height : y;
75848
75849         } else if (display == 'under' || display == 'over') {
75850             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
75851             bb = item.sprite.getBBox();
75852             bb.width = bb.width || (radius * 2);
75853             bb.height = bb.height || (radius * 2);
75854             y = y + (display == 'over'? -bb.height : bb.height);
75855             //correct label position to fit into the box
75856             bb = label.getBBox();
75857             width = bb.width/2;
75858             height = bb.height/2;
75859             x = x - width < bbox.x ? bbox.x + width : x;
75860             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
75861             y = y - height < bbox.y? bbox.y + height : y;
75862             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
75863         }
75864
75865         if (!chart.animate) {
75866             label.setAttributes({
75867                 x: x,
75868                 y: y
75869             }, true);
75870             label.show(true);
75871         }
75872         else {
75873             if (resizing) {
75874                 anim = item.sprite.getActiveAnimation();
75875                 if (anim) {
75876                     anim.on('afteranimate', function() {
75877                         label.setAttributes({
75878                             x: x,
75879                             y: y
75880                         }, true);
75881                         label.show(true);
75882                     });
75883                 }
75884                 else {
75885                     label.show(true);
75886                 }
75887             }
75888             else {
75889                 me.onAnimate(label, {
75890                     to: {
75891                         x: x,
75892                         y: y
75893                     }
75894                 });
75895             }
75896         }
75897     },
75898
75899     // @private callback for when placing a callout sprite.
75900     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
75901         var me = this,
75902             chart = me.chart,
75903             surface = chart.surface,
75904             resizing = chart.resizing,
75905             config = me.callouts,
75906             items = me.items,
75907             cur = item.point,
75908             normal,
75909             bbox = callout.label.getBBox(),
75910             offsetFromViz = 30,
75911             offsetToSide = 10,
75912             offsetBox = 3,
75913             boxx, boxy, boxw, boxh,
75914             p, clipRect = me.bbox,
75915             x, y;
75916
75917         //position
75918         normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
75919         x = cur[0] + normal[0] * offsetFromViz;
75920         y = cur[1] + normal[1] * offsetFromViz;
75921
75922         //box position and dimensions
75923         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
75924         boxy = y - bbox.height /2 - offsetBox;
75925         boxw = bbox.width + 2 * offsetBox;
75926         boxh = bbox.height + 2 * offsetBox;
75927
75928         //now check if we're out of bounds and invert the normal vector correspondingly
75929         //this may add new overlaps between labels (but labels won't be out of bounds).
75930         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
75931             normal[0] *= -1;
75932         }
75933         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
75934             normal[1] *= -1;
75935         }
75936
75937         //update positions
75938         x = cur[0] + normal[0] * offsetFromViz;
75939         y = cur[1] + normal[1] * offsetFromViz;
75940
75941         //update box position and dimensions
75942         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
75943         boxy = y - bbox.height /2 - offsetBox;
75944         boxw = bbox.width + 2 * offsetBox;
75945         boxh = bbox.height + 2 * offsetBox;
75946
75947         if (chart.animate) {
75948             //set the line from the middle of the pie to the box.
75949             me.onAnimate(callout.lines, {
75950                 to: {
75951                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
75952                 }
75953             }, true);
75954             //set box position
75955             me.onAnimate(callout.box, {
75956                 to: {
75957                     x: boxx,
75958                     y: boxy,
75959                     width: boxw,
75960                     height: boxh
75961                 }
75962             }, true);
75963             //set text position
75964             me.onAnimate(callout.label, {
75965                 to: {
75966                     x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
75967                     y: y
75968                 }
75969             }, true);
75970         } else {
75971             //set the line from the middle of the pie to the box.
75972             callout.lines.setAttributes({
75973                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
75974             }, true);
75975             //set box position
75976             callout.box.setAttributes({
75977                 x: boxx,
75978                 y: boxy,
75979                 width: boxw,
75980                 height: boxh
75981             }, true);
75982             //set text position
75983             callout.label.setAttributes({
75984                 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
75985                 y: y
75986             }, true);
75987         }
75988         for (p in callout) {
75989             callout[p].show(true);
75990         }
75991     },
75992
75993     // @private handles sprite animation for the series.
75994     onAnimate: function(sprite, attr) {
75995         sprite.show();
75996         return this.callParent(arguments);
75997     },
75998
75999     isItemInPoint: function(x, y, item) {
76000         var point,
76001             tolerance = 10,
76002             abs = Math.abs;
76003
76004         function dist(point) {
76005             var dx = abs(point[0] - x),
76006                 dy = abs(point[1] - y);
76007             return Math.sqrt(dx * dx + dy * dy);
76008         }
76009         point = item.point;
76010         return (point[0] - tolerance <= x && point[0] + tolerance >= x &&
76011             point[1] - tolerance <= y && point[1] + tolerance >= y);
76012     }
76013 });
76014
76015
76016 /**
76017  * @class Ext.chart.theme.Base
76018  * Provides default colors for non-specified things. Should be sub-classed when creating new themes.
76019  * @ignore
76020  */
76021 Ext.define('Ext.chart.theme.Base', {
76022
76023     /* Begin Definitions */
76024
76025     requires: ['Ext.chart.theme.Theme'],
76026
76027     /* End Definitions */
76028
76029     constructor: function(config) {
76030         Ext.chart.theme.call(this, config, {
76031             background: false,
76032             axis: {
76033                 stroke: '#444',
76034                 'stroke-width': 1
76035             },
76036             axisLabelTop: {
76037                 fill: '#444',
76038                 font: '12px Arial, Helvetica, sans-serif',
76039                 spacing: 2,
76040                 padding: 5,
76041                 renderer: function(v) { return v; }
76042             },
76043             axisLabelRight: {
76044                 fill: '#444',
76045                 font: '12px Arial, Helvetica, sans-serif',
76046                 spacing: 2,
76047                 padding: 5,
76048                 renderer: function(v) { return v; }
76049             },
76050             axisLabelBottom: {
76051                 fill: '#444',
76052                 font: '12px Arial, Helvetica, sans-serif',
76053                 spacing: 2,
76054                 padding: 5,
76055                 renderer: function(v) { return v; }
76056             },
76057             axisLabelLeft: {
76058                 fill: '#444',
76059                 font: '12px Arial, Helvetica, sans-serif',
76060                 spacing: 2,
76061                 padding: 5,
76062                 renderer: function(v) { return v; }
76063             },
76064             axisTitleTop: {
76065                 font: 'bold 18px Arial',
76066                 fill: '#444'
76067             },
76068             axisTitleRight: {
76069                 font: 'bold 18px Arial',
76070                 fill: '#444',
76071                 rotate: {
76072                     x:0, y:0,
76073                     degrees: 270
76074                 }
76075             },
76076             axisTitleBottom: {
76077                 font: 'bold 18px Arial',
76078                 fill: '#444'
76079             },
76080             axisTitleLeft: {
76081                 font: 'bold 18px Arial',
76082                 fill: '#444',
76083                 rotate: {
76084                     x:0, y:0,
76085                     degrees: 270
76086                 }
76087             },
76088             series: {
76089                 'stroke-width': 0
76090             },
76091             seriesLabel: {
76092                 font: '12px Arial',
76093                 fill: '#333'
76094             },
76095             marker: {
76096                 stroke: '#555',
76097                 fill: '#000',
76098                 radius: 3,
76099                 size: 3
76100             },
76101             colors: [ "#94ae0a", "#115fa6","#a61120", "#ff8809", "#ffd13e", "#a61187", "#24ad9a", "#7c7474", "#a66111"],
76102             seriesThemes: [{
76103                 fill: "#115fa6"
76104             }, {
76105                 fill: "#94ae0a"
76106             }, {
76107                 fill: "#a61120"
76108             }, {
76109                 fill: "#ff8809"
76110             }, {
76111                 fill: "#ffd13e"
76112             }, {
76113                 fill: "#a61187"
76114             }, {
76115                 fill: "#24ad9a"
76116             }, {
76117                 fill: "#7c7474"
76118             }, {
76119                 fill: "#a66111"
76120             }],
76121             markerThemes: [{
76122                 fill: "#115fa6",
76123                 type: 'circle' 
76124             }, {
76125                 fill: "#94ae0a",
76126                 type: 'cross'
76127             }, {
76128                 fill: "#a61120",
76129                 type: 'plus'
76130             }]
76131         });
76132     }
76133 }, function() {
76134     var palette = ['#b1da5a', '#4ce0e7', '#e84b67', '#da5abd', '#4d7fe6', '#fec935'],
76135         names = ['Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow'],
76136         i = 0, j = 0, l = palette.length, themes = Ext.chart.theme,
76137         categories = [['#f0a50a', '#c20024', '#2044ba', '#810065', '#7eae29'],
76138                       ['#6d9824', '#87146e', '#2a9196', '#d39006', '#1e40ac'],
76139                       ['#fbbc29', '#ce2e4e', '#7e0062', '#158b90', '#57880e'],
76140                       ['#ef5773', '#fcbd2a', '#4f770d', '#1d3eaa', '#9b001f'],
76141                       ['#7eae29', '#fdbe2a', '#910019', '#27b4bc', '#d74dbc'],
76142                       ['#44dce1', '#0b2592', '#996e05', '#7fb325', '#b821a1']],
76143         cats = categories.length;
76144     
76145     //Create themes from base colors
76146     for (; i < l; i++) {
76147         themes[names[i]] = (function(color) {
76148             return Ext.extend(themes.Base, {
76149                 constructor: function(config) {
76150                     themes.Base.prototype.constructor.call(this, Ext.apply({
76151                         baseColor: color
76152                     }, config));
76153                 }
76154             });
76155         })(palette[i]);
76156     }
76157     
76158     //Create theme from color array
76159     for (i = 0; i < cats; i++) {
76160         themes['Category' + (i + 1)] = (function(category) {
76161             return Ext.extend(themes.Base, {
76162                 constructor: function(config) {
76163                     themes.Base.prototype.constructor.call(this, Ext.apply({
76164                         colors: category
76165                     }, config));
76166                 }
76167             });
76168         })(categories[i]);
76169     }
76170 });
76171
76172 /**
76173  * @author Ed Spencer
76174  *
76175  * Small helper class to make creating {@link Ext.data.Store}s from Array data easier. An ArrayStore will be
76176  * automatically configured with a {@link Ext.data.reader.Array}.
76177  *
76178  * A store configuration would be something like:
76179  *
76180  *     var store = Ext.create('Ext.data.ArrayStore', {
76181  *         // store configs
76182  *         autoDestroy: true,
76183  *         storeId: 'myStore',
76184  *         // reader configs
76185  *         idIndex: 0,
76186  *         fields: [
76187  *            'company',
76188  *            {name: 'price', type: 'float'},
76189  *            {name: 'change', type: 'float'},
76190  *            {name: 'pctChange', type: 'float'},
76191  *            {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
76192  *         ]
76193  *     });
76194  *
76195  * This store is configured to consume a returned object of the form:
76196  *
76197  *     var myData = [
76198  *         ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
76199  *         ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
76200  *         ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
76201  *         ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
76202  *         ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
76203  *     ];
76204  *
76205  * An object literal of this form could also be used as the {@link #data} config option.
76206  *
76207  * **Note:** This class accepts all of the configuration options of {@link Ext.data.reader.Array ArrayReader}.
76208  */
76209 Ext.define('Ext.data.ArrayStore', {
76210     extend: 'Ext.data.Store',
76211     alias: 'store.array',
76212     uses: ['Ext.data.reader.Array'],
76213
76214     constructor: function(config) {
76215         config = config || {};
76216
76217         Ext.applyIf(config, {
76218             proxy: {
76219                 type: 'memory',
76220                 reader: 'array'
76221             }
76222         });
76223
76224         this.callParent([config]);
76225     },
76226
76227     loadData: function(data, append) {
76228         if (this.expandData === true) {
76229             var r = [],
76230                 i = 0,
76231                 ln = data.length;
76232
76233             for (; i < ln; i++) {
76234                 r[r.length] = [data[i]];
76235             }
76236
76237             data = r;
76238         }
76239
76240         this.callParent([data, append]);
76241     }
76242 }, function() {
76243     // backwards compat
76244     Ext.data.SimpleStore = Ext.data.ArrayStore;
76245     // Ext.reg('simplestore', Ext.data.SimpleStore);
76246 });
76247
76248 /**
76249  * @author Ed Spencer
76250  * @class Ext.data.Batch
76251  *
76252  * <p>Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the 'operationcomplete' event
76253  * after the completion of each Operation, and the 'complete' event when all Operations have been successfully executed. Fires an 'exception'
76254  * event if any of the Operations encounter an exception.</p>
76255  *
76256  * <p>Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes</p>
76257  *
76258  */
76259 Ext.define('Ext.data.Batch', {
76260     mixins: {
76261         observable: 'Ext.util.Observable'
76262     },
76263
76264     /**
76265      * @property {Boolean} autoStart
76266      * True to immediately start processing the batch as soon as it is constructed.
76267      */
76268     autoStart: false,
76269
76270     /**
76271      * @property {Number} current
76272      * The index of the current operation being executed
76273      */
76274     current: -1,
76275
76276     /**
76277      * @property {Number} total
76278      * The total number of operations in this batch. Read only
76279      */
76280     total: 0,
76281
76282     /**
76283      * @property {Boolean} isRunning
76284      * True if the batch is currently running
76285      */
76286     isRunning: false,
76287
76288     /**
76289      * @property {Boolean} isComplete
76290      * True if this batch has been executed completely
76291      */
76292     isComplete: false,
76293
76294     /**
76295      * @property {Boolean} hasException
76296      * True if this batch has encountered an exception. This is cleared at the start of each operation
76297      */
76298     hasException: false,
76299
76300     /**
76301      * @property {Boolean} pauseOnException
76302      * True to automatically pause the execution of the batch if any operation encounters an exception
76303      */
76304     pauseOnException: true,
76305
76306     /**
76307      * Creates new Batch object.
76308      * @param {Object} [config] Config object
76309      */
76310     constructor: function(config) {
76311         var me = this;
76312
76313         me.addEvents(
76314           /**
76315            * @event complete
76316            * Fired when all operations of this batch have been completed
76317            * @param {Ext.data.Batch} batch The batch object
76318            * @param {Object} operation The last operation that was executed
76319            */
76320           'complete',
76321
76322           /**
76323            * @event exception
76324            * Fired when a operation encountered an exception
76325            * @param {Ext.data.Batch} batch The batch object
76326            * @param {Object} operation The operation that encountered the exception
76327            */
76328           'exception',
76329
76330           /**
76331            * @event operationcomplete
76332            * Fired when each operation of the batch completes
76333            * @param {Ext.data.Batch} batch The batch object
76334            * @param {Object} operation The operation that just completed
76335            */
76336           'operationcomplete'
76337         );
76338
76339         me.mixins.observable.constructor.call(me, config);
76340
76341         /**
76342          * Ordered array of operations that will be executed by this batch
76343          * @property {Ext.data.Operation[]} operations
76344          */
76345         me.operations = [];
76346     },
76347
76348     /**
76349      * Adds a new operation to this batch
76350      * @param {Object} operation The {@link Ext.data.Operation Operation} object
76351      */
76352     add: function(operation) {
76353         this.total++;
76354
76355         operation.setBatch(this);
76356
76357         this.operations.push(operation);
76358     },
76359
76360     /**
76361      * Kicks off the execution of the batch, continuing from the next operation if the previous
76362      * operation encountered an exception, or if execution was paused
76363      */
76364     start: function() {
76365         this.hasException = false;
76366         this.isRunning = true;
76367
76368         this.runNextOperation();
76369     },
76370
76371     /**
76372      * @private
76373      * Runs the next operation, relative to this.current.
76374      */
76375     runNextOperation: function() {
76376         this.runOperation(this.current + 1);
76377     },
76378
76379     /**
76380      * Pauses execution of the batch, but does not cancel the current operation
76381      */
76382     pause: function() {
76383         this.isRunning = false;
76384     },
76385
76386     /**
76387      * Executes a operation by its numeric index
76388      * @param {Number} index The operation index to run
76389      */
76390     runOperation: function(index) {
76391         var me = this,
76392             operations = me.operations,
76393             operation  = operations[index],
76394             onProxyReturn;
76395
76396         if (operation === undefined) {
76397             me.isRunning  = false;
76398             me.isComplete = true;
76399             me.fireEvent('complete', me, operations[operations.length - 1]);
76400         } else {
76401             me.current = index;
76402
76403             onProxyReturn = function(operation) {
76404                 var hasException = operation.hasException();
76405
76406                 if (hasException) {
76407                     me.hasException = true;
76408                     me.fireEvent('exception', me, operation);
76409                 } else {
76410                     me.fireEvent('operationcomplete', me, operation);
76411                 }
76412
76413                 if (hasException && me.pauseOnException) {
76414                     me.pause();
76415                 } else {
76416                     operation.setCompleted();
76417                     me.runNextOperation();
76418                 }
76419             };
76420
76421             operation.setStarted();
76422
76423             me.proxy[operation.action](operation, onProxyReturn, me);
76424         }
76425     }
76426 });
76427 /**
76428  * @author Ed Spencer
76429  * @class Ext.data.BelongsToAssociation
76430  * @extends Ext.data.Association
76431  *
76432  * Represents a many to one association with another model. The owner model is expected to have
76433  * a foreign key which references the primary key of the associated model:
76434  *
76435  *     Ext.define('Category', {
76436  *         extend: 'Ext.data.Model',
76437  *         fields: [
76438  *             { name: 'id',   type: 'int' },
76439  *             { name: 'name', type: 'string' }
76440  *         ]
76441  *     });
76442  *
76443  *     Ext.define('Product', {
76444  *         extend: 'Ext.data.Model',
76445  *         fields: [
76446  *             { name: 'id',          type: 'int' },
76447  *             { name: 'category_id', type: 'int' },
76448  *             { name: 'name',        type: 'string' }
76449  *         ],
76450  *         // we can use the belongsTo shortcut on the model to create a belongsTo association
76451  *         associations: [
76452  *             { type: 'belongsTo', model: 'Category' }
76453  *         ]
76454  *     });
76455  *
76456  * In the example above we have created models for Products and Categories, and linked them together
76457  * by saying that each Product belongs to a Category. This automatically links each Product to a Category
76458  * based on the Product's category_id, and provides new functions on the Product model:
76459  *
76460  * ## Generated getter function
76461  *
76462  * The first function that is added to the owner model is a getter function:
76463  *
76464  *     var product = new Product({
76465  *         id: 100,
76466  *         category_id: 20,
76467  *         name: 'Sneakers'
76468  *     });
76469  *
76470  *     product.getCategory(function(category, operation) {
76471  *         // do something with the category object
76472  *         alert(category.get('id')); // alerts 20
76473  *     }, this);
76474  *
76475  * The getCategory function was created on the Product model when we defined the association. This uses the
76476  * Category's configured {@link Ext.data.proxy.Proxy proxy} to load the Category asynchronously, calling the provided
76477  * callback when it has loaded.
76478  *
76479  * The new getCategory function will also accept an object containing success, failure and callback properties
76480  * - callback will always be called, success will only be called if the associated model was loaded successfully
76481  * and failure will only be called if the associatied model could not be loaded:
76482  *
76483  *     product.getCategory({
76484  *         callback: function(category, operation) {}, // a function that will always be called
76485  *         success : function(category, operation) {}, // a function that will only be called if the load succeeded
76486  *         failure : function(category, operation) {}, // a function that will only be called if the load did not succeed
76487  *         scope   : this // optionally pass in a scope object to execute the callbacks in
76488  *     });
76489  *
76490  * In each case above the callbacks are called with two arguments - the associated model instance and the
76491  * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
76492  * useful when the instance could not be loaded.
76493  *
76494  * ## Generated setter function
76495  *
76496  * The second generated function sets the associated model instance - if only a single argument is passed to
76497  * the setter then the following two calls are identical:
76498  *
76499  *     // this call...
76500  *     product.setCategory(10);
76501  *
76502  *     // is equivalent to this call:
76503  *     product.set('category_id', 10);
76504  *
76505  * If we pass in a second argument, the model will be automatically saved and the second argument passed to
76506  * the owner model's {@link Ext.data.Model#save save} method:
76507  *
76508  *     product.setCategory(10, function(product, operation) {
76509  *         // the product has been saved
76510  *         alert(product.get('category_id')); //now alerts 10
76511  *     });
76512  *
76513  *     //alternative syntax:
76514  *     product.setCategory(10, {
76515  *         callback: function(product, operation), // a function that will always be called
76516  *         success : function(product, operation), // a function that will only be called if the load succeeded
76517  *         failure : function(product, operation), // a function that will only be called if the load did not succeed
76518  *         scope   : this //optionally pass in a scope object to execute the callbacks in
76519  *     })
76520  *
76521  * ## Customisation
76522  *
76523  * Associations reflect on the models they are linking to automatically set up properties such as the
76524  * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
76525  *
76526  *     Ext.define('Product', {
76527  *         fields: [...],
76528  *
76529  *         associations: [
76530  *             { type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id' }
76531  *         ]
76532  *     });
76533  *
76534  * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
76535  * with our own settings. Usually this will not be needed.
76536  */
76537 Ext.define('Ext.data.BelongsToAssociation', {
76538     extend: 'Ext.data.Association',
76539
76540     alias: 'association.belongsto',
76541
76542     /**
76543      * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
76544      * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
76545      * model called Product would set up a product_id foreign key.
76546      *
76547      *     Ext.define('Order', {
76548      *         extend: 'Ext.data.Model',
76549      *         fields: ['id', 'date'],
76550      *         hasMany: 'Product'
76551      *     });
76552      *
76553      *     Ext.define('Product', {
76554      *         extend: 'Ext.data.Model',
76555      *         fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
76556      *         belongsTo: 'Group'
76557      *     });
76558      *     var product = new Product({
76559      *         id: 1,
76560      *         name: 'Product 1',
76561      *         order_id: 22
76562      *     }, 1);
76563      *     product.getOrder(); // Will make a call to the server asking for order_id 22
76564      *
76565      */
76566
76567     /**
76568      * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
76569      * Defaults to 'get' + the name of the foreign model, e.g. getCategory
76570      */
76571
76572     /**
76573      * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
76574      * Defaults to 'set' + the name of the foreign model, e.g. setCategory
76575      */
76576
76577     /**
76578      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
76579      * Use 'belongsTo' to create a HasManyAssocation
76580      *
76581      *     associations: [{
76582      *         type: 'belongsTo',
76583      *         model: 'User'
76584      *     }]
76585      */
76586     constructor: function(config) {
76587         this.callParent(arguments);
76588
76589         var me             = this,
76590             ownerProto     = me.ownerModel.prototype,
76591             associatedName = me.associatedName,
76592             getterName     = me.getterName || 'get' + associatedName,
76593             setterName     = me.setterName || 'set' + associatedName;
76594
76595         Ext.applyIf(me, {
76596             name        : associatedName,
76597             foreignKey  : associatedName.toLowerCase() + "_id",
76598             instanceName: associatedName + 'BelongsToInstance',
76599             associationKey: associatedName.toLowerCase()
76600         });
76601
76602         ownerProto[getterName] = me.createGetter();
76603         ownerProto[setterName] = me.createSetter();
76604     },
76605
76606     /**
76607      * @private
76608      * Returns a setter function to be placed on the owner model's prototype
76609      * @return {Function} The setter function
76610      */
76611     createSetter: function() {
76612         var me              = this,
76613             ownerModel      = me.ownerModel,
76614             associatedModel = me.associatedModel,
76615             foreignKey      = me.foreignKey,
76616             primaryKey      = me.primaryKey;
76617
76618         //'this' refers to the Model instance inside this function
76619         return function(value, options, scope) {
76620             this.set(foreignKey, value);
76621
76622             if (typeof options == 'function') {
76623                 options = {
76624                     callback: options,
76625                     scope: scope || this
76626                 };
76627             }
76628
76629             if (Ext.isObject(options)) {
76630                 return this.save(options);
76631             }
76632         };
76633     },
76634
76635     /**
76636      * @private
76637      * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
76638      * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
76639      * @return {Function} The getter function
76640      */
76641     createGetter: function() {
76642         var me              = this,
76643             ownerModel      = me.ownerModel,
76644             associatedName  = me.associatedName,
76645             associatedModel = me.associatedModel,
76646             foreignKey      = me.foreignKey,
76647             primaryKey      = me.primaryKey,
76648             instanceName    = me.instanceName;
76649
76650         //'this' refers to the Model instance inside this function
76651         return function(options, scope) {
76652             options = options || {};
76653
76654             var model = this,
76655                 foreignKeyId = model.get(foreignKey),
76656                 instance,
76657                 args;
76658
76659             if (model[instanceName] === undefined) {
76660                 instance = Ext.ModelManager.create({}, associatedName);
76661                 instance.set(primaryKey, foreignKeyId);
76662
76663                 if (typeof options == 'function') {
76664                     options = {
76665                         callback: options,
76666                         scope: scope || model
76667                     };
76668                 }
76669
76670                 associatedModel.load(foreignKeyId, options);
76671                 model[instanceName] = associatedModel;
76672                 return associatedModel;
76673             } else {
76674                 instance = model[instanceName];
76675                 args = [instance];
76676                 scope = scope || model;
76677
76678                 //TODO: We're duplicating the callback invokation code that the instance.load() call above
76679                 //makes here - ought to be able to normalize this - perhaps by caching at the Model.load layer
76680                 //instead of the association layer.
76681                 Ext.callback(options, scope, args);
76682                 Ext.callback(options.success, scope, args);
76683                 Ext.callback(options.failure, scope, args);
76684                 Ext.callback(options.callback, scope, args);
76685
76686                 return instance;
76687             }
76688         };
76689     },
76690
76691     /**
76692      * Read associated data
76693      * @private
76694      * @param {Ext.data.Model} record The record we're writing to
76695      * @param {Ext.data.reader.Reader} reader The reader for the associated model
76696      * @param {Object} associationData The raw associated data
76697      */
76698     read: function(record, reader, associationData){
76699         record[this.instanceName] = reader.read([associationData]).records[0];
76700     }
76701 });
76702
76703 /**
76704  * @class Ext.data.BufferStore
76705  * @extends Ext.data.Store
76706  * @ignore
76707  */
76708 Ext.define('Ext.data.BufferStore', {
76709     extend: 'Ext.data.Store',
76710     alias: 'store.buffer',
76711     sortOnLoad: false,
76712     filterOnLoad: false,
76713     
76714     constructor: function() {
76715         Ext.Error.raise('The BufferStore class has been deprecated. Instead, specify the buffered config option on Ext.data.Store');
76716     }
76717 });
76718 /**
76719  * Ext.Direct aims to streamline communication between the client and server by providing a single interface that
76720  * reduces the amount of common code typically required to validate data and handle returned data packets (reading data,
76721  * error conditions, etc).
76722  *
76723  * The Ext.direct namespace includes several classes for a closer integration with the server-side. The Ext.data
76724  * namespace also includes classes for working with Ext.data.Stores which are backed by data from an Ext.Direct method.
76725  *
76726  * # Specification
76727  *
76728  * For additional information consult the [Ext.Direct Specification][1].
76729  *
76730  * # Providers
76731  *
76732  * Ext.Direct uses a provider architecture, where one or more providers are used to transport data to and from the
76733  * server. There are several providers that exist in the core at the moment:
76734  *
76735  * - {@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations
76736  * - {@link Ext.direct.PollingProvider PollingProvider} for repeated requests
76737  * - {@link Ext.direct.RemotingProvider RemotingProvider} exposes server side on the client.
76738  *
76739  * A provider does not need to be invoked directly, providers are added via {@link Ext.direct.Manager}.{@link #addProvider}.
76740  *
76741  * # Router
76742  *
76743  * Ext.Direct utilizes a "router" on the server to direct requests from the client to the appropriate server-side
76744  * method. Because the Ext.Direct API is completely platform-agnostic, you could completely swap out a Java based server
76745  * solution and replace it with one that uses C# without changing the client side JavaScript at all.
76746  *
76747  * # Server side events
76748  *
76749  * Custom events from the server may be handled by the client by adding listeners, for example:
76750  *
76751  *     {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
76752  *
76753  *     // add a handler for a 'message' event sent by the server
76754  *     Ext.direct.Manager.on('message', function(e){
76755  *         out.append(String.format('<p><i>{0}</i></p>', e.data));
76756  *         out.el.scrollTo('t', 100000, true);
76757  *     });
76758  *
76759  *    [1]: http://sencha.com/products/extjs/extdirect
76760  *
76761  * @singleton
76762  * @alternateClassName Ext.Direct
76763  */
76764 Ext.define('Ext.direct.Manager', {
76765
76766     /* Begin Definitions */
76767     singleton: true,
76768
76769     mixins: {
76770         observable: 'Ext.util.Observable'
76771     },
76772
76773     requires: ['Ext.util.MixedCollection'],
76774
76775     statics: {
76776         exceptions: {
76777             TRANSPORT: 'xhr',
76778             PARSE: 'parse',
76779             LOGIN: 'login',
76780             SERVER: 'exception'
76781         }
76782     },
76783
76784     /* End Definitions */
76785
76786     constructor: function(){
76787         var me = this;
76788
76789         me.addEvents(
76790             /**
76791              * @event event
76792              * Fires after an event.
76793              * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
76794              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
76795              */
76796             'event',
76797             /**
76798              * @event exception
76799              * Fires after an event exception.
76800              * @param {Ext.direct.Event} e The event type that occurred.
76801              */
76802             'exception'
76803         );
76804         me.transactions = Ext.create('Ext.util.MixedCollection');
76805         me.providers = Ext.create('Ext.util.MixedCollection');
76806
76807         me.mixins.observable.constructor.call(me);
76808     },
76809
76810     /**
76811      * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods. If the provider
76812      * is not already connected, it will auto-connect.
76813      *
76814      *     var pollProv = new Ext.direct.PollingProvider({
76815      *         url: 'php/poll2.php'
76816      *     });
76817      *
76818      *     Ext.direct.Manager.addProvider({
76819      *         "type":"remoting",       // create a {@link Ext.direct.RemotingProvider}
76820      *         "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
76821      *         "actions":{              // each property within the actions object represents a Class
76822      *             "TestAction":[       // array of methods within each server side Class
76823      *             {
76824      *                 "name":"doEcho", // name of method
76825      *                 "len":1
76826      *             },{
76827      *                 "name":"multiply",
76828      *                 "len":1
76829      *             },{
76830      *                 "name":"doForm",
76831      *                 "formHandler":true, // handle form on server with Ext.Direct.Transaction
76832      *                 "len":1
76833      *             }]
76834      *         },
76835      *         "namespace":"myApplication",// namespace to create the Remoting Provider in
76836      *     },{
76837      *         type: 'polling', // create a {@link Ext.direct.PollingProvider}
76838      *         url:  'php/poll.php'
76839      *     }, pollProv); // reference to previously created instance
76840      *
76841      * @param {Ext.direct.Provider/Object...} provider
76842      * Accepts any number of Provider descriptions (an instance or config object for
76843      * a Provider). Each Provider description instructs Ext.Directhow to create
76844      * client-side stub methods.
76845      */
76846     addProvider : function(provider){
76847         var me = this,
76848             args = arguments,
76849             i = 0,
76850             len;
76851
76852         if (args.length > 1) {
76853             for (len = args.length; i < len; ++i) {
76854                 me.addProvider(args[i]);
76855             }
76856             return;
76857         }
76858
76859         // if provider has not already been instantiated
76860         if (!provider.isProvider) {
76861             provider = Ext.create('direct.' + provider.type + 'provider', provider);
76862         }
76863         me.providers.add(provider);
76864         provider.on('data', me.onProviderData, me);
76865
76866
76867         if (!provider.isConnected()) {
76868             provider.connect();
76869         }
76870
76871         return provider;
76872     },
76873
76874     /**
76875      * Retrieves a {@link Ext.direct.Provider provider} by the **{@link Ext.direct.Provider#id id}** specified when the
76876      * provider is {@link #addProvider added}.
76877      * @param {String/Ext.direct.Provider} id The id of the provider, or the provider instance.
76878      */
76879     getProvider : function(id){
76880         return id.isProvider ? id : this.providers.get(id);
76881     },
76882
76883     /**
76884      * Removes the provider.
76885      * @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider.
76886      * @return {Ext.direct.Provider} The provider, null if not found.
76887      */
76888     removeProvider : function(provider){
76889         var me = this,
76890             providers = me.providers;
76891
76892         provider = provider.isProvider ? provider : providers.get(provider);
76893
76894         if (provider) {
76895             provider.un('data', me.onProviderData, me);
76896             providers.remove(provider);
76897             return provider;
76898         }
76899         return null;
76900     },
76901
76902     /**
76903      * Adds a transaction to the manager.
76904      * @private
76905      * @param {Ext.direct.Transaction} transaction The transaction to add
76906      * @return {Ext.direct.Transaction} transaction
76907      */
76908     addTransaction: function(transaction){
76909         this.transactions.add(transaction);
76910         return transaction;
76911     },
76912
76913     /**
76914      * Removes a transaction from the manager.
76915      * @private
76916      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove
76917      * @return {Ext.direct.Transaction} transaction
76918      */
76919     removeTransaction: function(transaction){
76920         transaction = this.getTransaction(transaction);
76921         this.transactions.remove(transaction);
76922         return transaction;
76923     },
76924
76925     /**
76926      * Gets a transaction
76927      * @private
76928      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to get
76929      * @return {Ext.direct.Transaction}
76930      */
76931     getTransaction: function(transaction){
76932         return transaction.isTransaction ? transaction : this.transactions.get(transaction);
76933     },
76934
76935     onProviderData : function(provider, event){
76936         var me = this,
76937             i = 0,
76938             len;
76939
76940         if (Ext.isArray(event)) {
76941             for (len = event.length; i < len; ++i) {
76942                 me.onProviderData(provider, event[i]);
76943             }
76944             return;
76945         }
76946         if (event.name && event.name != 'event' && event.name != 'exception') {
76947             me.fireEvent(event.name, event);
76948         } else if (event.status === false) {
76949             me.fireEvent('exception', event);
76950         }
76951         me.fireEvent('event', event, provider);
76952     }
76953 }, function(){
76954     // Backwards compatibility
76955     Ext.Direct = Ext.direct.Manager;
76956 });
76957
76958 /**
76959  * This class is used to send requests to the server using {@link Ext.direct.Manager Ext.Direct}. When a
76960  * request is made, the transport mechanism is handed off to the appropriate
76961  * {@link Ext.direct.RemotingProvider Provider} to complete the call.
76962  *
76963  * # Specifying the function
76964  *
76965  * This proxy expects a Direct remoting method to be passed in order to be able to complete requests.
76966  * This can be done by specifying the {@link #directFn} configuration. This will use the same direct
76967  * method for all requests. Alternatively, you can provide an {@link #api} configuration. This
76968  * allows you to specify a different remoting method for each CRUD action.
76969  *
76970  * # Parameters
76971  *
76972  * This proxy provides options to help configure which parameters will be sent to the server.
76973  * By specifying the {@link #paramsAsHash} option, it will send an object literal containing each
76974  * of the passed parameters. The {@link #paramOrder} option can be used to specify the order in which
76975  * the remoting method parameters are passed.
76976  *
76977  * # Example Usage
76978  *
76979  *     Ext.define('User', {
76980  *         extend: 'Ext.data.Model',
76981  *         fields: ['firstName', 'lastName'],
76982  *         proxy: {
76983  *             type: 'direct',
76984  *             directFn: MyApp.getUsers,
76985  *             paramOrder: 'id' // Tells the proxy to pass the id as the first parameter to the remoting method.
76986  *         }
76987  *     });
76988  *     User.load(1);
76989  */
76990 Ext.define('Ext.data.proxy.Direct', {
76991     /* Begin Definitions */
76992
76993     extend: 'Ext.data.proxy.Server',
76994     alternateClassName: 'Ext.data.DirectProxy',
76995
76996     alias: 'proxy.direct',
76997
76998     requires: ['Ext.direct.Manager'],
76999
77000     /* End Definitions */
77001
77002     /**
77003      * @cfg {String/String[]} paramOrder
77004      * Defaults to undefined. A list of params to be executed server side.  Specify the params in the order in
77005      * which they must be executed on the server-side as either (1) an Array of String values, or (2) a String
77006      * of params delimited by either whitespace, comma, or pipe. For example, any of the following would be
77007      * acceptable:
77008      *
77009      *     paramOrder: ['param1','param2','param3']
77010      *     paramOrder: 'param1 param2 param3'
77011      *     paramOrder: 'param1,param2,param3'
77012      *     paramOrder: 'param1|param2|param'
77013      */
77014     paramOrder: undefined,
77015
77016     /**
77017      * @cfg {Boolean} paramsAsHash
77018      * Send parameters as a collection of named arguments.
77019      * Providing a {@link #paramOrder} nullifies this configuration.
77020      */
77021     paramsAsHash: true,
77022
77023     /**
77024      * @cfg {Function} directFn
77025      * Function to call when executing a request.  directFn is a simple alternative to defining the api configuration-parameter
77026      * for Store's which will not implement a full CRUD api.
77027      */
77028     directFn : undefined,
77029
77030     /**
77031      * @cfg {Object} api
77032      * The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
77033      * function call.
77034      */
77035
77036     /**
77037      * @cfg {Object} extraParams
77038      * Extra parameters that will be included on every read request. Individual requests with params
77039      * of the same name will override these params when they are in conflict.
77040      */
77041
77042     // private
77043     paramOrderRe: /[\s,|]/,
77044
77045     constructor: function(config){
77046         var me = this;
77047
77048         Ext.apply(me, config);
77049         if (Ext.isString(me.paramOrder)) {
77050             me.paramOrder = me.paramOrder.split(me.paramOrderRe);
77051         }
77052         me.callParent(arguments);
77053     },
77054
77055     doRequest: function(operation, callback, scope) {
77056         var me = this,
77057             writer = me.getWriter(),
77058             request = me.buildRequest(operation, callback, scope),
77059             fn = me.api[request.action]  || me.directFn,
77060             args = [],
77061             params = request.params,
77062             paramOrder = me.paramOrder,
77063             method,
77064             i = 0,
77065             len;
77066
77067         if (!fn) {
77068             Ext.Error.raise('No direct function specified for this proxy');
77069         }
77070
77071         if (operation.allowWrite()) {
77072             request = writer.write(request);
77073         }
77074
77075         if (operation.action == 'read') {
77076             // We need to pass params
77077             method = fn.directCfg.method;
77078
77079             if (method.ordered) {
77080                 if (method.len > 0) {
77081                     if (paramOrder) {
77082                         for (len = paramOrder.length; i < len; ++i) {
77083                             args.push(params[paramOrder[i]]);
77084                         }
77085                     } else if (me.paramsAsHash) {
77086                         args.push(params);
77087                     }
77088                 }
77089             } else {
77090                 args.push(params);
77091             }
77092         } else {
77093             args.push(request.jsonData);
77094         }
77095
77096         Ext.apply(request, {
77097             args: args,
77098             directFn: fn
77099         });
77100         args.push(me.createRequestCallback(request, operation, callback, scope), me);
77101         fn.apply(window, args);
77102     },
77103
77104     /*
77105      * Inherit docs. We don't apply any encoding here because
77106      * all of the direct requests go out as jsonData
77107      */
77108     applyEncoding: function(value){
77109         return value;
77110     },
77111
77112     createRequestCallback: function(request, operation, callback, scope){
77113         var me = this;
77114
77115         return function(data, event){
77116             me.processResponse(event.status, operation, request, event, callback, scope);
77117         };
77118     },
77119
77120     // inherit docs
77121     extractResponseData: function(response){
77122         return Ext.isDefined(response.result) ? response.result : response.data;
77123     },
77124
77125     // inherit docs
77126     setException: function(operation, response) {
77127         operation.setException(response.message);
77128     },
77129
77130     // inherit docs
77131     buildUrl: function(){
77132         return '';
77133     }
77134 });
77135
77136 /**
77137  * Small helper class to create an {@link Ext.data.Store} configured with an {@link Ext.data.proxy.Direct}
77138  * and {@link Ext.data.reader.Json} to make interacting with an {@link Ext.direct.Manager} server-side
77139  * {@link Ext.direct.Provider Provider} easier. To create a different proxy/reader combination create a basic
77140  * {@link Ext.data.Store} configured as needed.
77141  *
77142  * **Note:** Although they are not listed, this class inherits all of the config options of:
77143  *
77144  * - **{@link Ext.data.Store Store}**
77145  *
77146  * - **{@link Ext.data.reader.Json JsonReader}**
77147  *
77148  *   - **{@link Ext.data.reader.Json#root root}**
77149  *   - **{@link Ext.data.reader.Json#idProperty idProperty}**
77150  *   - **{@link Ext.data.reader.Json#totalProperty totalProperty}**
77151  *
77152  * - **{@link Ext.data.proxy.Direct DirectProxy}**
77153  *
77154  *   - **{@link Ext.data.proxy.Direct#directFn directFn}**
77155  *   - **{@link Ext.data.proxy.Direct#paramOrder paramOrder}**
77156  *   - **{@link Ext.data.proxy.Direct#paramsAsHash paramsAsHash}**
77157  *
77158  */
77159 Ext.define('Ext.data.DirectStore', {
77160     /* Begin Definitions */
77161     
77162     extend: 'Ext.data.Store',
77163     
77164     alias: 'store.direct',
77165     
77166     requires: ['Ext.data.proxy.Direct'],
77167    
77168     /* End Definitions */
77169
77170     constructor : function(config){
77171         config = Ext.apply({}, config);
77172         if (!config.proxy) {
77173             var proxy = {
77174                 type: 'direct',
77175                 reader: {
77176                     type: 'json'
77177                 }
77178             };
77179             Ext.copyTo(proxy, config, 'paramOrder,paramsAsHash,directFn,api,simpleSortMode');
77180             Ext.copyTo(proxy.reader, config, 'totalProperty,root,idProperty');
77181             config.proxy = proxy;
77182         }
77183         this.callParent([config]);
77184     }    
77185 });
77186
77187 /**
77188  * General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
77189  * {@link #ordinalize ordinalizes} words. Sample usage:
77190  *
77191  *     //turning singular words into plurals
77192  *     Ext.util.Inflector.pluralize('word'); //'words'
77193  *     Ext.util.Inflector.pluralize('person'); //'people'
77194  *     Ext.util.Inflector.pluralize('sheep'); //'sheep'
77195  *
77196  *     //turning plurals into singulars
77197  *     Ext.util.Inflector.singularize('words'); //'word'
77198  *     Ext.util.Inflector.singularize('people'); //'person'
77199  *     Ext.util.Inflector.singularize('sheep'); //'sheep'
77200  *
77201  *     //ordinalizing numbers
77202  *     Ext.util.Inflector.ordinalize(11); //"11th"
77203  *     Ext.util.Inflector.ordinalize(21); //"21th"
77204  *     Ext.util.Inflector.ordinalize(1043); //"1043rd"
77205  *
77206  * # Customization
77207  *
77208  * The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
77209  * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
77210  * Here is how we might add a rule that pluralizes "ox" to "oxen":
77211  *
77212  *     Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
77213  *
77214  * Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string. In
77215  * this case, the regular expression will only match the string "ox", and will replace that match with "oxen". Here's
77216  * how we could add the inverse rule:
77217  *
77218  *     Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
77219  *
77220  * Note that the ox/oxen rules are present by default.
77221  */
77222 Ext.define('Ext.util.Inflector', {
77223
77224     /* Begin Definitions */
77225
77226     singleton: true,
77227
77228     /* End Definitions */
77229
77230     /**
77231      * @private
77232      * The registered plural tuples. Each item in the array should contain two items - the first must be a regular
77233      * expression that matchers the singular form of a word, the second must be a String that replaces the matched
77234      * part of the regular expression. This is managed by the {@link #plural} method.
77235      * @property {Array} plurals
77236      */
77237     plurals: [
77238         [(/(quiz)$/i),                "$1zes"  ],
77239         [(/^(ox)$/i),                 "$1en"   ],
77240         [(/([m|l])ouse$/i),           "$1ice"  ],
77241         [(/(matr|vert|ind)ix|ex$/i),  "$1ices" ],
77242         [(/(x|ch|ss|sh)$/i),          "$1es"   ],
77243         [(/([^aeiouy]|qu)y$/i),       "$1ies"  ],
77244         [(/(hive)$/i),                "$1s"    ],
77245         [(/(?:([^f])fe|([lr])f)$/i),  "$1$2ves"],
77246         [(/sis$/i),                   "ses"    ],
77247         [(/([ti])um$/i),              "$1a"    ],
77248         [(/(buffal|tomat|potat)o$/i), "$1oes"  ],
77249         [(/(bu)s$/i),                 "$1ses"  ],
77250         [(/(alias|status|sex)$/i),    "$1es"   ],
77251         [(/(octop|vir)us$/i),         "$1i"    ],
77252         [(/(ax|test)is$/i),           "$1es"   ],
77253         [(/^person$/),                "people" ],
77254         [(/^man$/),                   "men"    ],
77255         [(/^(child)$/),               "$1ren"  ],
77256         [(/s$/i),                     "s"      ],
77257         [(/$/),                       "s"      ]
77258     ],
77259
77260     /**
77261      * @private
77262      * The set of registered singular matchers. Each item in the array should contain two items - the first must be a
77263      * regular expression that matches the plural form of a word, the second must be a String that replaces the
77264      * matched part of the regular expression. This is managed by the {@link #singular} method.
77265      * @property {Array} singulars
77266      */
77267     singulars: [
77268       [(/(quiz)zes$/i),                                                    "$1"     ],
77269       [(/(matr)ices$/i),                                                   "$1ix"   ],
77270       [(/(vert|ind)ices$/i),                                               "$1ex"   ],
77271       [(/^(ox)en/i),                                                       "$1"     ],
77272       [(/(alias|status)es$/i),                                             "$1"     ],
77273       [(/(octop|vir)i$/i),                                                 "$1us"   ],
77274       [(/(cris|ax|test)es$/i),                                             "$1is"   ],
77275       [(/(shoe)s$/i),                                                      "$1"     ],
77276       [(/(o)es$/i),                                                        "$1"     ],
77277       [(/(bus)es$/i),                                                      "$1"     ],
77278       [(/([m|l])ice$/i),                                                   "$1ouse" ],
77279       [(/(x|ch|ss|sh)es$/i),                                               "$1"     ],
77280       [(/(m)ovies$/i),                                                     "$1ovie" ],
77281       [(/(s)eries$/i),                                                     "$1eries"],
77282       [(/([^aeiouy]|qu)ies$/i),                                            "$1y"    ],
77283       [(/([lr])ves$/i),                                                    "$1f"    ],
77284       [(/(tive)s$/i),                                                      "$1"     ],
77285       [(/(hive)s$/i),                                                      "$1"     ],
77286       [(/([^f])ves$/i),                                                    "$1fe"   ],
77287       [(/(^analy)ses$/i),                                                  "$1sis"  ],
77288       [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
77289       [(/([ti])a$/i),                                                      "$1um"   ],
77290       [(/(n)ews$/i),                                                       "$1ews"  ],
77291       [(/people$/i),                                                       "person" ],
77292       [(/s$/i),                                                            ""       ]
77293     ],
77294
77295     /**
77296      * @private
77297      * The registered uncountable words
77298      * @property {String[]} uncountable
77299      */
77300      uncountable: [
77301         "sheep",
77302         "fish",
77303         "series",
77304         "species",
77305         "money",
77306         "rice",
77307         "information",
77308         "equipment",
77309         "grass",
77310         "mud",
77311         "offspring",
77312         "deer",
77313         "means"
77314     ],
77315
77316     /**
77317      * Adds a new singularization rule to the Inflector. See the intro docs for more information
77318      * @param {RegExp} matcher The matcher regex
77319      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
77320      */
77321     singular: function(matcher, replacer) {
77322         this.singulars.unshift([matcher, replacer]);
77323     },
77324
77325     /**
77326      * Adds a new pluralization rule to the Inflector. See the intro docs for more information
77327      * @param {RegExp} matcher The matcher regex
77328      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
77329      */
77330     plural: function(matcher, replacer) {
77331         this.plurals.unshift([matcher, replacer]);
77332     },
77333
77334     /**
77335      * Removes all registered singularization rules
77336      */
77337     clearSingulars: function() {
77338         this.singulars = [];
77339     },
77340
77341     /**
77342      * Removes all registered pluralization rules
77343      */
77344     clearPlurals: function() {
77345         this.plurals = [];
77346     },
77347
77348     /**
77349      * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
77350      * @param {String} word The word to test
77351      * @return {Boolean} True if the word is transnumeral
77352      */
77353     isTransnumeral: function(word) {
77354         return Ext.Array.indexOf(this.uncountable, word) != -1;
77355     },
77356
77357     /**
77358      * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
77359      * @param {String} word The word to pluralize
77360      * @return {String} The pluralized form of the word
77361      */
77362     pluralize: function(word) {
77363         if (this.isTransnumeral(word)) {
77364             return word;
77365         }
77366
77367         var plurals = this.plurals,
77368             length  = plurals.length,
77369             tuple, regex, i;
77370
77371         for (i = 0; i < length; i++) {
77372             tuple = plurals[i];
77373             regex = tuple[0];
77374
77375             if (regex == word || (regex.test && regex.test(word))) {
77376                 return word.replace(regex, tuple[1]);
77377             }
77378         }
77379
77380         return word;
77381     },
77382
77383     /**
77384      * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
77385      * @param {String} word The word to singularize
77386      * @return {String} The singularized form of the word
77387      */
77388     singularize: function(word) {
77389         if (this.isTransnumeral(word)) {
77390             return word;
77391         }
77392
77393         var singulars = this.singulars,
77394             length    = singulars.length,
77395             tuple, regex, i;
77396
77397         for (i = 0; i < length; i++) {
77398             tuple = singulars[i];
77399             regex = tuple[0];
77400
77401             if (regex == word || (regex.test && regex.test(word))) {
77402                 return word.replace(regex, tuple[1]);
77403             }
77404         }
77405
77406         return word;
77407     },
77408
77409     /**
77410      * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
77411      * package
77412      * @param {String} word The word to classify
77413      * @return {String} The classified version of the word
77414      */
77415     classify: function(word) {
77416         return Ext.String.capitalize(this.singularize(word));
77417     },
77418
77419     /**
77420      * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
77421      * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
77422      * @param {Number} number The number to ordinalize
77423      * @return {String} The ordinalized number
77424      */
77425     ordinalize: function(number) {
77426         var parsed = parseInt(number, 10),
77427             mod10  = parsed % 10,
77428             mod100 = parsed % 100;
77429
77430         //11 through 13 are a special case
77431         if (11 <= mod100 && mod100 <= 13) {
77432             return number + "th";
77433         } else {
77434             switch(mod10) {
77435                 case 1 : return number + "st";
77436                 case 2 : return number + "nd";
77437                 case 3 : return number + "rd";
77438                 default: return number + "th";
77439             }
77440         }
77441     }
77442 }, function() {
77443     //aside from the rules above, there are a number of words that have irregular pluralization so we add them here
77444     var irregulars = {
77445             alumnus: 'alumni',
77446             cactus : 'cacti',
77447             focus  : 'foci',
77448             nucleus: 'nuclei',
77449             radius: 'radii',
77450             stimulus: 'stimuli',
77451             ellipsis: 'ellipses',
77452             paralysis: 'paralyses',
77453             oasis: 'oases',
77454             appendix: 'appendices',
77455             index: 'indexes',
77456             beau: 'beaux',
77457             bureau: 'bureaux',
77458             tableau: 'tableaux',
77459             woman: 'women',
77460             child: 'children',
77461             man: 'men',
77462             corpus:     'corpora',
77463             criterion: 'criteria',
77464             curriculum: 'curricula',
77465             genus: 'genera',
77466             memorandum: 'memoranda',
77467             phenomenon: 'phenomena',
77468             foot: 'feet',
77469             goose: 'geese',
77470             tooth: 'teeth',
77471             antenna: 'antennae',
77472             formula: 'formulae',
77473             nebula: 'nebulae',
77474             vertebra: 'vertebrae',
77475             vita: 'vitae'
77476         },
77477         singular;
77478
77479     for (singular in irregulars) {
77480         this.plural(singular, irregulars[singular]);
77481         this.singular(irregulars[singular], singular);
77482     }
77483 });
77484 /**
77485  * @author Ed Spencer
77486  * @class Ext.data.HasManyAssociation
77487  * @extends Ext.data.Association
77488  * 
77489  * <p>Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:</p>
77490  * 
77491 <pre><code>
77492 Ext.define('Product', {
77493     extend: 'Ext.data.Model',
77494     fields: [
77495         {name: 'id',      type: 'int'},
77496         {name: 'user_id', type: 'int'},
77497         {name: 'name',    type: 'string'}
77498     ]
77499 });
77500
77501 Ext.define('User', {
77502     extend: 'Ext.data.Model',
77503     fields: [
77504         {name: 'id',   type: 'int'},
77505         {name: 'name', type: 'string'}
77506     ],
77507     // we can use the hasMany shortcut on the model to create a hasMany association
77508     hasMany: {model: 'Product', name: 'products'}
77509 });
77510 </pre></code>
77511
77512  * <p>Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives
77513  * us a new function on every User instance, in this case the function is called 'products' because that is the name
77514  * we specified in the association configuration above.</p>
77515  * 
77516  * <p>This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load
77517  * only Products for the given model instance:</p>
77518  * 
77519 <pre><code>
77520 //first, we load up a User with id of 1
77521 var user = Ext.create('User', {id: 1, name: 'Ed'});
77522
77523 //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
77524 //the created store is automatically scoped to the set of Products for the User with id of 1
77525 var products = user.products();
77526
77527 //we still have all of the usual Store functions, for example it's easy to add a Product for this User
77528 products.add({
77529     name: 'Another Product'
77530 });
77531
77532 //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
77533 products.sync();
77534 </code></pre>
77535  * 
77536  * <p>The new Store is only instantiated the first time you call products() to conserve memory and processing time,
77537  * though calling products() a second time returns the same store instance.</p>
77538  * 
77539  * <p><u>Custom filtering</u></p>
77540  * 
77541  * <p>The Store is automatically furnished with a filter - by default this filter tells the store to only return
77542  * records where the associated model's foreign key matches the owner model's primary key. For example, if a User
77543  * with ID = 100 hasMany Products, the filter loads only Products with user_id == 100.</p>
77544  * 
77545  * <p>Sometimes we want to filter by another field - for example in the case of a Twitter search application we may
77546  * have models for Search and Tweet:</p>
77547  * 
77548 <pre><code>
77549 Ext.define('Search', {
77550     extend: 'Ext.data.Model',
77551     fields: [
77552         'id', 'query'
77553     ],
77554
77555     hasMany: {
77556         model: 'Tweet',
77557         name : 'tweets',
77558         filterProperty: 'query'
77559     }
77560 });
77561
77562 Ext.define('Tweet', {
77563     extend: 'Ext.data.Model',
77564     fields: [
77565         'id', 'text', 'from_user'
77566     ]
77567 });
77568
77569 //returns a Store filtered by the filterProperty
77570 var store = new Search({query: 'Sencha Touch'}).tweets();
77571 </code></pre>
77572  * 
77573  * <p>The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
77574  * equivalent to this:</p>
77575  * 
77576 <pre><code>
77577 var store = Ext.create('Ext.data.Store', {
77578     model: 'Tweet',
77579     filters: [
77580         {
77581             property: 'query',
77582             value   : 'Sencha Touch'
77583         }
77584     ]
77585 });
77586 </code></pre>
77587  */
77588 Ext.define('Ext.data.HasManyAssociation', {
77589     extend: 'Ext.data.Association',
77590     requires: ['Ext.util.Inflector'],
77591
77592     alias: 'association.hasmany',
77593
77594     /**
77595      * @cfg {String} foreignKey The name of the foreign key on the associated model that links it to the owner
77596      * model. Defaults to the lowercased name of the owner model plus "_id", e.g. an association with a where a
77597      * model called Group hasMany Users would create 'group_id' as the foreign key. When the remote store is loaded,
77598      * the store is automatically filtered so that only records with a matching foreign key are included in the 
77599      * resulting child store. This can be overridden by specifying the {@link #filterProperty}.
77600      * <pre><code>
77601 Ext.define('Group', {
77602     extend: 'Ext.data.Model',
77603     fields: ['id', 'name'],
77604     hasMany: 'User'
77605 });
77606
77607 Ext.define('User', {
77608     extend: 'Ext.data.Model',
77609     fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
77610     belongsTo: 'Group'
77611 });
77612      * </code></pre>
77613      */
77614     
77615     /**
77616      * @cfg {String} name The name of the function to create on the owner model to retrieve the child store.
77617      * If not specified, the pluralized name of the child model is used.
77618      * <pre><code>
77619 // This will create a users() method on any Group model instance
77620 Ext.define('Group', {
77621     extend: 'Ext.data.Model',
77622     fields: ['id', 'name'],
77623     hasMany: 'User'
77624 });
77625 var group = new Group();
77626 console.log(group.users());
77627
77628 // The method to retrieve the users will now be getUserList
77629 Ext.define('Group', {
77630     extend: 'Ext.data.Model',
77631     fields: ['id', 'name'],
77632     hasMany: {model: 'User', name: 'getUserList'}
77633 });
77634 var group = new Group();
77635 console.log(group.getUserList());
77636      * </code></pre>
77637      */
77638     
77639     /**
77640      * @cfg {Object} storeConfig Optional configuration object that will be passed to the generated Store. Defaults to 
77641      * undefined.
77642      */
77643     
77644     /**
77645      * @cfg {String} filterProperty Optionally overrides the default filter that is set up on the associated Store. If
77646      * this is not set, a filter is automatically created which filters the association based on the configured 
77647      * {@link #foreignKey}. See intro docs for more details. Defaults to undefined
77648      */
77649     
77650     /**
77651      * @cfg {Boolean} autoLoad True to automatically load the related store from a remote source when instantiated.
77652      * Defaults to <tt>false</tt>.
77653      */
77654     
77655     /**
77656      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
77657      * Use 'hasMany' to create a HasManyAssocation
77658      * <pre><code>
77659 associations: [{
77660     type: 'hasMany',
77661     model: 'User'
77662 }]
77663      * </code></pre>
77664      */
77665     
77666     constructor: function(config) {
77667         var me = this,
77668             ownerProto,
77669             name;
77670             
77671         me.callParent(arguments);
77672         
77673         me.name = me.name || Ext.util.Inflector.pluralize(me.associatedName.toLowerCase());
77674         
77675         ownerProto = me.ownerModel.prototype;
77676         name = me.name;
77677         
77678         Ext.applyIf(me, {
77679             storeName : name + "Store",
77680             foreignKey: me.ownerName.toLowerCase() + "_id"
77681         });
77682         
77683         ownerProto[name] = me.createStore();
77684     },
77685     
77686     /**
77687      * @private
77688      * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
77689      * by the owner model's primary key - e.g. in a hasMany association where Group hasMany Users, this function
77690      * returns a Store configured to return the filtered set of a single Group's Users.
77691      * @return {Function} The store-generating function
77692      */
77693     createStore: function() {
77694         var that            = this,
77695             associatedModel = that.associatedModel,
77696             storeName       = that.storeName,
77697             foreignKey      = that.foreignKey,
77698             primaryKey      = that.primaryKey,
77699             filterProperty  = that.filterProperty,
77700             autoLoad        = that.autoLoad,
77701             storeConfig     = that.storeConfig || {};
77702         
77703         return function() {
77704             var me = this,
77705                 config, filter,
77706                 modelDefaults = {};
77707                 
77708             if (me[storeName] === undefined) {
77709                 if (filterProperty) {
77710                     filter = {
77711                         property  : filterProperty,
77712                         value     : me.get(filterProperty),
77713                         exactMatch: true
77714                     };
77715                 } else {
77716                     filter = {
77717                         property  : foreignKey,
77718                         value     : me.get(primaryKey),
77719                         exactMatch: true
77720                     };
77721                 }
77722                 
77723                 modelDefaults[foreignKey] = me.get(primaryKey);
77724                 
77725                 config = Ext.apply({}, storeConfig, {
77726                     model        : associatedModel,
77727                     filters      : [filter],
77728                     remoteFilter : false,
77729                     modelDefaults: modelDefaults
77730                 });
77731                 
77732                 me[storeName] = Ext.create('Ext.data.Store', config);
77733                 if (autoLoad) {
77734                     me[storeName].load();
77735                 }
77736             }
77737             
77738             return me[storeName];
77739         };
77740     },
77741     
77742     /**
77743      * Read associated data
77744      * @private
77745      * @param {Ext.data.Model} record The record we're writing to
77746      * @param {Ext.data.reader.Reader} reader The reader for the associated model
77747      * @param {Object} associationData The raw associated data
77748      */
77749     read: function(record, reader, associationData){
77750         var store = record[this.name](),
77751             inverse;
77752     
77753         store.add(reader.read(associationData).records);
77754     
77755         //now that we've added the related records to the hasMany association, set the inverse belongsTo
77756         //association on each of them if it exists
77757         inverse = this.associatedModel.prototype.associations.findBy(function(assoc){
77758             return assoc.type === 'belongsTo' && assoc.associatedName === record.$className;
77759         });
77760     
77761         //if the inverse association was found, set it now on each record we've just created
77762         if (inverse) {
77763             store.data.each(function(associatedRecord){
77764                 associatedRecord[inverse.instanceName] = record;
77765             });
77766         }
77767     }
77768 });
77769 /**
77770  * @class Ext.data.JsonP
77771  * @singleton
77772  * This class is used to create JSONP requests. JSONP is a mechanism that allows for making
77773  * requests for data cross domain. More information is available <a href="http://en.wikipedia.org/wiki/JSONP">here</a>.
77774  */
77775 Ext.define('Ext.data.JsonP', {
77776
77777     /* Begin Definitions */
77778
77779     singleton: true,
77780
77781     statics: {
77782         requestCount: 0,
77783         requests: {}
77784     },
77785
77786     /* End Definitions */
77787
77788     /**
77789      * @property timeout
77790      * @type Number
77791      * A default timeout for any JsonP requests. If the request has not completed in this time the
77792      * failure callback will be fired. The timeout is in ms. Defaults to <tt>30000</tt>.
77793      */
77794     timeout: 30000,
77795
77796     /**
77797      * @property disableCaching
77798      * @type Boolean
77799      * True to add a unique cache-buster param to requests. Defaults to <tt>true</tt>.
77800      */
77801     disableCaching: true,
77802
77803     /**
77804      * @property disableCachingParam
77805      * @type String
77806      * Change the parameter which is sent went disabling caching through a cache buster. Defaults to <tt>'_dc'</tt>.
77807      */
77808     disableCachingParam: '_dc',
77809
77810     /**
77811      * @property callbackKey
77812      * @type String
77813      * Specifies the GET parameter that will be sent to the server containing the function name to be executed when
77814      * the request completes. Defaults to <tt>callback</tt>. Thus, a common request will be in the form of
77815      * url?callback=Ext.data.JsonP.callback1
77816      */
77817     callbackKey: 'callback',
77818
77819     /**
77820      * Makes a JSONP request.
77821      * @param {Object} options An object which may contain the following properties. Note that options will
77822      * take priority over any defaults that are specified in the class.
77823      * <ul>
77824      * <li><b>url</b> : String <div class="sub-desc">The URL to request.</div></li>
77825      * <li><b>params</b> : Object (Optional)<div class="sub-desc">An object containing a series of
77826      * key value pairs that will be sent along with the request.</div></li>
77827      * <li><b>timeout</b> : Number (Optional) <div class="sub-desc">See {@link #timeout}</div></li>
77828      * <li><b>callbackKey</b> : String (Optional) <div class="sub-desc">See {@link #callbackKey}</div></li>
77829      * <li><b>callbackName</b> : String (Optional) <div class="sub-desc">The function name to use for this request.
77830      * By default this name will be auto-generated: Ext.data.JsonP.callback1, Ext.data.JsonP.callback2, etc.
77831      * Setting this option to "my_name" will force the function name to be Ext.data.JsonP.my_name.
77832      * Use this if you want deterministic behavior, but be careful - the callbackName should be different
77833      * in each JsonP request that you make.</div></li>
77834      * <li><b>disableCaching</b> : Boolean (Optional) <div class="sub-desc">See {@link #disableCaching}</div></li>
77835      * <li><b>disableCachingParam</b> : String (Optional) <div class="sub-desc">See {@link #disableCachingParam}</div></li>
77836      * <li><b>success</b> : Function (Optional) <div class="sub-desc">A function to execute if the request succeeds.</div></li>
77837      * <li><b>failure</b> : Function (Optional) <div class="sub-desc">A function to execute if the request fails.</div></li>
77838      * <li><b>callback</b> : Function (Optional) <div class="sub-desc">A function to execute when the request
77839      * completes, whether it is a success or failure.</div></li>
77840      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
77841      * which to execute the callbacks: The "this" object for the callback function. Defaults to the browser window.</div></li>
77842      * </ul>
77843      * @return {Object} request An object containing the request details.
77844      */
77845     request: function(options){
77846         options = Ext.apply({}, options);
77847
77848         if (!options.url) {
77849             Ext.Error.raise('A url must be specified for a JSONP request.');
77850         }
77851
77852         var me = this,
77853             disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
77854             cacheParam = options.disableCachingParam || me.disableCachingParam,
77855             id = ++me.statics().requestCount,
77856             callbackName = options.callbackName || 'callback' + id,
77857             callbackKey = options.callbackKey || me.callbackKey,
77858             timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
77859             params = Ext.apply({}, options.params),
77860             url = options.url,
77861             name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext',
77862             request,
77863             script;
77864
77865         params[callbackKey] = name + '.data.JsonP.' + callbackName;
77866         if (disableCaching) {
77867             params[cacheParam] = new Date().getTime();
77868         }
77869
77870         script = me.createScript(url, params);
77871
77872         me.statics().requests[id] = request = {
77873             url: url,
77874             params: params,
77875             script: script,
77876             id: id,
77877             scope: options.scope,
77878             success: options.success,
77879             failure: options.failure,
77880             callback: options.callback,
77881             callbackName: callbackName
77882         };
77883
77884         if (timeout > 0) {
77885             request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
77886         }
77887
77888         me.setupErrorHandling(request);
77889         me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
77890         Ext.getHead().appendChild(script);
77891         return request;
77892     },
77893
77894     /**
77895      * Abort a request. If the request parameter is not specified all open requests will
77896      * be aborted.
77897      * @param {Object/String} request (Optional) The request to abort
77898      */
77899     abort: function(request){
77900         var requests = this.statics().requests,
77901             key;
77902
77903         if (request) {
77904             if (!request.id) {
77905                 request = requests[request];
77906             }
77907             this.abort(request);
77908         } else {
77909             for (key in requests) {
77910                 if (requests.hasOwnProperty(key)) {
77911                     this.abort(requests[key]);
77912                 }
77913             }
77914         }
77915     },
77916
77917     /**
77918      * Sets up error handling for the script
77919      * @private
77920      * @param {Object} request The request
77921      */
77922     setupErrorHandling: function(request){
77923         request.script.onerror = Ext.bind(this.handleError, this, [request]);
77924     },
77925
77926     /**
77927      * Handles any aborts when loading the script
77928      * @private
77929      * @param {Object} request The request
77930      */
77931     handleAbort: function(request){
77932         request.errorType = 'abort';
77933         this.handleResponse(null, request);
77934     },
77935
77936     /**
77937      * Handles any script errors when loading the script
77938      * @private
77939      * @param {Object} request The request
77940      */
77941     handleError: function(request){
77942         request.errorType = 'error';
77943         this.handleResponse(null, request);
77944     },
77945
77946     /**
77947      * Cleans up anu script handling errors
77948      * @private
77949      * @param {Object} request The request
77950      */
77951     cleanupErrorHandling: function(request){
77952         request.script.onerror = null;
77953     },
77954
77955     /**
77956      * Handle any script timeouts
77957      * @private
77958      * @param {Object} request The request
77959      */
77960     handleTimeout: function(request){
77961         request.errorType = 'timeout';
77962         this.handleResponse(null, request);
77963     },
77964
77965     /**
77966      * Handle a successful response
77967      * @private
77968      * @param {Object} result The result from the request
77969      * @param {Object} request The request
77970      */
77971     handleResponse: function(result, request){
77972
77973         var success = true;
77974
77975         if (request.timeout) {
77976             clearTimeout(request.timeout);
77977         }
77978         delete this[request.callbackName];
77979         delete this.statics()[request.id];
77980         this.cleanupErrorHandling(request);
77981         Ext.fly(request.script).remove();
77982
77983         if (request.errorType) {
77984             success = false;
77985             Ext.callback(request.failure, request.scope, [request.errorType]);
77986         } else {
77987             Ext.callback(request.success, request.scope, [result]);
77988         }
77989         Ext.callback(request.callback, request.scope, [success, result, request.errorType]);
77990     },
77991
77992     /**
77993      * Create the script tag
77994      * @private
77995      * @param {String} url The url of the request
77996      * @param {Object} params Any extra params to be sent
77997      */
77998     createScript: function(url, params) {
77999         var script = document.createElement('script');
78000         script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
78001         script.setAttribute("async", true);
78002         script.setAttribute("type", "text/javascript");
78003         return script;
78004     }
78005 });
78006
78007 /**
78008  * @class Ext.data.JsonPStore
78009  * @extends Ext.data.Store
78010  * @private
78011  * <p>Small helper class to make creating {@link Ext.data.Store}s from different domain JSON data easier.
78012  * A JsonPStore will be automatically configured with a {@link Ext.data.reader.Json} and a {@link Ext.data.proxy.JsonP JsonPProxy}.</p>
78013  * <p>A store configuration would be something like:<pre><code>
78014 var store = new Ext.data.JsonPStore({
78015     // store configs
78016     autoDestroy: true,
78017     storeId: 'myStore',
78018
78019     // proxy configs
78020     url: 'get-images.php',
78021
78022     // reader configs
78023     root: 'images',
78024     idProperty: 'name',
78025     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
78026 });
78027  * </code></pre></p>
78028  * <p>This store is configured to consume a returned object of the form:<pre><code>
78029 stcCallback({
78030     images: [
78031         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
78032         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
78033     ]
78034 })
78035  * </code></pre>
78036  * <p>Where stcCallback is the callback name passed in the request to the remote domain. See {@link Ext.data.proxy.JsonP JsonPProxy}
78037  * for details of how this works.</p>
78038  * An object literal of this form could also be used as the {@link #data} config option.</p>
78039  * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
78040  * <b>{@link Ext.data.reader.Json JsonReader}</b> and <b>{@link Ext.data.proxy.JsonP JsonPProxy}</b>.</p>
78041  * @xtype jsonpstore
78042  */
78043 Ext.define('Ext.data.JsonPStore', {
78044     extend: 'Ext.data.Store',
78045     alias : 'store.jsonp',
78046
78047     /**
78048      * @cfg {Ext.data.DataReader} reader @hide
78049      */
78050     constructor: function(config) {
78051         this.callParent(Ext.apply(config, {
78052             reader: Ext.create('Ext.data.reader.Json', config),
78053             proxy : Ext.create('Ext.data.proxy.JsonP', config)
78054         }));
78055     }
78056 });
78057
78058 /**
78059  * This class is used as a set of methods that are applied to the prototype of a
78060  * Model to decorate it with a Node API. This means that models used in conjunction with a tree
78061  * will have all of the tree related methods available on the model. In general this class will
78062  * not be used directly by the developer. This class also creates extra fields on the model if
78063  * they do not exist, to help maintain the tree state and UI. These fields are documented as
78064  * config options.
78065  */
78066 Ext.define('Ext.data.NodeInterface', {
78067     requires: ['Ext.data.Field'],
78068
78069     /**
78070      * @cfg {String} parentId
78071      * ID of parent node.
78072      */
78073
78074     /**
78075      * @cfg {Number} index
78076      * The position of the node inside its parent. When parent has 4 children and the node is third amongst them,
78077      * index will be 2.
78078      */
78079
78080     /**
78081      * @cfg {Number} depth
78082      * The number of parents this node has. A root node has depth 0, a child of it depth 1, and so on...
78083      */
78084
78085     /**
78086      * @cfg {Boolean} [expanded=false]
78087      * True if the node is expanded.
78088      */
78089
78090     /**
78091      * @cfg {Boolean} [expandable=false]
78092      * Set to true to allow for expanding/collapsing of this node.
78093      */
78094
78095     /**
78096      * @cfg {Boolean} [checked=null]
78097      * Set to true or false to show a checkbox alongside this node.
78098      */
78099
78100     /**
78101      * @cfg {Boolean} [leaf=false]
78102      * Set to true to indicate that this child can have no children. The expand icon/arrow will then not be
78103      * rendered for this node.
78104      */
78105
78106     /**
78107      * @cfg {String} cls
78108      * CSS class to apply for this node.
78109      */
78110
78111     /**
78112      * @cfg {String} iconCls
78113      * CSS class to apply for this node's icon.
78114      */
78115
78116     /**
78117      * @cfg {String} icon
78118      * URL for this node's icon.
78119      */
78120
78121     /**
78122      * @cfg {Boolean} root
78123      * True if this is the root node.
78124      */
78125
78126     /**
78127      * @cfg {Boolean} isLast
78128      * True if this is the last node.
78129      */
78130
78131     /**
78132      * @cfg {Boolean} isFirst
78133      * True if this is the first node.
78134      */
78135
78136     /**
78137      * @cfg {Boolean} [allowDrop=true]
78138      * Set to false to deny dropping on this node.
78139      */
78140
78141     /**
78142      * @cfg {Boolean} [allowDrag=true]
78143      * Set to false to deny dragging of this node.
78144      */
78145
78146     /**
78147      * @cfg {Boolean} [loaded=false]
78148      * True if the node has finished loading.
78149      */
78150
78151     /**
78152      * @cfg {Boolean} [loading=false]
78153      * True if the node is currently loading.
78154      */
78155
78156     /**
78157      * @cfg {String} href
78158      * An URL for a link that's created when this config is specified.
78159      */
78160
78161     /**
78162      * @cfg {String} hrefTarget
78163      * Target for link. Only applicable when {@link #href} also specified.
78164      */
78165
78166     /**
78167      * @cfg {String} qtip
78168      * Tooltip text to show on this node.
78169      */
78170
78171     /**
78172      * @cfg {String} qtitle
78173      * Tooltip title.
78174      */
78175
78176     /**
78177      * @cfg {String} text
78178      * The text for to show on node label.
78179      */
78180
78181     /**
78182      * @cfg {Ext.data.NodeInterface[]} children
78183      * Array of child nodes.
78184      */
78185
78186
78187     /**
78188      * @property nextSibling
78189      * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
78190      */
78191
78192     /**
78193      * @property previousSibling
78194      * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
78195      */
78196
78197     /**
78198      * @property parentNode
78199      * A reference to this node's parent node. `null` if this node is the root node.
78200      */
78201
78202     /**
78203      * @property lastChild
78204      * A reference to this node's last child node. `null` if this node has no children.
78205      */
78206
78207     /**
78208      * @property firstChild
78209      * A reference to this node's first child node. `null` if this node has no children.
78210      */
78211
78212     /**
78213      * @property childNodes
78214      * An array of this nodes children.  Array will be empty if this node has no chidren.
78215      */
78216
78217     statics: {
78218         /**
78219          * This method allows you to decorate a Record's prototype to implement the NodeInterface.
78220          * This adds a set of methods, new events, new properties and new fields on every Record
78221          * with the same Model as the passed Record.
78222          * @param {Ext.data.Model} record The Record you want to decorate the prototype of.
78223          * @static
78224          */
78225         decorate: function(record) {
78226             if (!record.isNode) {
78227                 // Apply the methods and fields to the prototype
78228                 // @TODO: clean this up to use proper class system stuff
78229                 var mgr = Ext.ModelManager,
78230                     modelName = record.modelName,
78231                     modelClass = mgr.getModel(modelName),
78232                     idName = modelClass.prototype.idProperty,
78233                     newFields = [],
78234                     i, newField, len;
78235
78236                 // Start by adding the NodeInterface methods to the Model's prototype
78237                 modelClass.override(this.getPrototypeBody());
78238                 newFields = this.applyFields(modelClass, [
78239                     {name: idName,       type: 'string',  defaultValue: null},
78240                     {name: 'parentId',   type: 'string',  defaultValue: null},
78241                     {name: 'index',      type: 'int',     defaultValue: null},
78242                     {name: 'depth',      type: 'int',     defaultValue: 0},
78243                     {name: 'expanded',   type: 'bool',    defaultValue: false, persist: false},
78244                     {name: 'expandable', type: 'bool',    defaultValue: true, persist: false},
78245                     {name: 'checked',    type: 'auto',    defaultValue: null},
78246                     {name: 'leaf',       type: 'bool',    defaultValue: false, persist: false},
78247                     {name: 'cls',        type: 'string',  defaultValue: null, persist: false},
78248                     {name: 'iconCls',    type: 'string',  defaultValue: null, persist: false},
78249                     {name: 'icon',       type: 'string',  defaultValue: null, persist: false},
78250                     {name: 'root',       type: 'boolean', defaultValue: false, persist: false},
78251                     {name: 'isLast',     type: 'boolean', defaultValue: false, persist: false},
78252                     {name: 'isFirst',    type: 'boolean', defaultValue: false, persist: false},
78253                     {name: 'allowDrop',  type: 'boolean', defaultValue: true, persist: false},
78254                     {name: 'allowDrag',  type: 'boolean', defaultValue: true, persist: false},
78255                     {name: 'loaded',     type: 'boolean', defaultValue: false, persist: false},
78256                     {name: 'loading',    type: 'boolean', defaultValue: false, persist: false},
78257                     {name: 'href',       type: 'string',  defaultValue: null, persist: false},
78258                     {name: 'hrefTarget', type: 'string',  defaultValue: null, persist: false},
78259                     {name: 'qtip',       type: 'string',  defaultValue: null, persist: false},
78260                     {name: 'qtitle',     type: 'string',  defaultValue: null, persist: false}
78261                 ]);
78262
78263                 len = newFields.length;
78264                 // Set default values
78265                 for (i = 0; i < len; ++i) {
78266                     newField = newFields[i];
78267                     if (record.get(newField.name) === undefined) {
78268                         record.data[newField.name] = newField.defaultValue;
78269                     }
78270                 }
78271             }
78272
78273             Ext.applyIf(record, {
78274                 firstChild: null,
78275                 lastChild: null,
78276                 parentNode: null,
78277                 previousSibling: null,
78278                 nextSibling: null,
78279                 childNodes: []
78280             });
78281             // Commit any fields so the record doesn't show as dirty initially
78282             record.commit(true);
78283
78284             record.enableBubble([
78285                 /**
78286                  * @event append
78287                  * Fires when a new child node is appended
78288                  * @param {Ext.data.NodeInterface} this This node
78289                  * @param {Ext.data.NodeInterface} node The newly appended node
78290                  * @param {Number} index The index of the newly appended node
78291                  */
78292                 "append",
78293
78294                 /**
78295                  * @event remove
78296                  * Fires when a child node is removed
78297                  * @param {Ext.data.NodeInterface} this This node
78298                  * @param {Ext.data.NodeInterface} node The removed node
78299                  */
78300                 "remove",
78301
78302                 /**
78303                  * @event move
78304                  * Fires when this node is moved to a new location in the tree
78305                  * @param {Ext.data.NodeInterface} this This node
78306                  * @param {Ext.data.NodeInterface} oldParent The old parent of this node
78307                  * @param {Ext.data.NodeInterface} newParent The new parent of this node
78308                  * @param {Number} index The index it was moved to
78309                  */
78310                 "move",
78311
78312                 /**
78313                  * @event insert
78314                  * Fires when a new child node is inserted.
78315                  * @param {Ext.data.NodeInterface} this This node
78316                  * @param {Ext.data.NodeInterface} node The child node inserted
78317                  * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before
78318                  */
78319                 "insert",
78320
78321                 /**
78322                  * @event beforeappend
78323                  * Fires before a new child is appended, return false to cancel the append.
78324                  * @param {Ext.data.NodeInterface} this This node
78325                  * @param {Ext.data.NodeInterface} node The child node to be appended
78326                  */
78327                 "beforeappend",
78328
78329                 /**
78330                  * @event beforeremove
78331                  * Fires before a child is removed, return false to cancel the remove.
78332                  * @param {Ext.data.NodeInterface} this This node
78333                  * @param {Ext.data.NodeInterface} node The child node to be removed
78334                  */
78335                 "beforeremove",
78336
78337                 /**
78338                  * @event beforemove
78339                  * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
78340                  * @param {Ext.data.NodeInterface} this This node
78341                  * @param {Ext.data.NodeInterface} oldParent The parent of this node
78342                  * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to
78343                  * @param {Number} index The index it is being moved to
78344                  */
78345                 "beforemove",
78346
78347                  /**
78348                   * @event beforeinsert
78349                   * Fires before a new child is inserted, return false to cancel the insert.
78350                   * @param {Ext.data.NodeInterface} this This node
78351                   * @param {Ext.data.NodeInterface} node The child node to be inserted
78352                   * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
78353                   */
78354                 "beforeinsert",
78355
78356                 /**
78357                  * @event expand
78358                  * Fires when this node is expanded.
78359                  * @param {Ext.data.NodeInterface} this The expanding node
78360                  */
78361                 "expand",
78362
78363                 /**
78364                  * @event collapse
78365                  * Fires when this node is collapsed.
78366                  * @param {Ext.data.NodeInterface} this The collapsing node
78367                  */
78368                 "collapse",
78369
78370                 /**
78371                  * @event beforeexpand
78372                  * Fires before this node is expanded.
78373                  * @param {Ext.data.NodeInterface} this The expanding node
78374                  */
78375                 "beforeexpand",
78376
78377                 /**
78378                  * @event beforecollapse
78379                  * Fires before this node is collapsed.
78380                  * @param {Ext.data.NodeInterface} this The collapsing node
78381                  */
78382                 "beforecollapse",
78383
78384                 /**
78385                  * @event sort
78386                  * Fires when this node's childNodes are sorted.
78387                  * @param {Ext.data.NodeInterface} this This node.
78388                  * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
78389                  */
78390                 "sort"
78391             ]);
78392
78393             return record;
78394         },
78395
78396         applyFields: function(modelClass, addFields) {
78397             var modelPrototype = modelClass.prototype,
78398                 fields = modelPrototype.fields,
78399                 keys = fields.keys,
78400                 ln = addFields.length,
78401                 addField, i, name,
78402                 newFields = [];
78403
78404             for (i = 0; i < ln; i++) {
78405                 addField = addFields[i];
78406                 if (!Ext.Array.contains(keys, addField.name)) {
78407                     addField = Ext.create('data.field', addField);
78408
78409                     newFields.push(addField);
78410                     fields.add(addField);
78411                 }
78412             }
78413
78414             return newFields;
78415         },
78416
78417         getPrototypeBody: function() {
78418             return {
78419                 isNode: true,
78420
78421                 /**
78422                  * Ensures that the passed object is an instance of a Record with the NodeInterface applied
78423                  * @return {Boolean}
78424                  */
78425                 createNode: function(node) {
78426                     if (Ext.isObject(node) && !node.isModel) {
78427                         node = Ext.ModelManager.create(node, this.modelName);
78428                     }
78429                     // Make sure the node implements the node interface
78430                     return Ext.data.NodeInterface.decorate(node);
78431                 },
78432
78433                 /**
78434                  * Returns true if this node is a leaf
78435                  * @return {Boolean}
78436                  */
78437                 isLeaf : function() {
78438                     return this.get('leaf') === true;
78439                 },
78440
78441                 /**
78442                  * Sets the first child of this node
78443                  * @private
78444                  * @param {Ext.data.NodeInterface} node
78445                  */
78446                 setFirstChild : function(node) {
78447                     this.firstChild = node;
78448                 },
78449
78450                 /**
78451                  * Sets the last child of this node
78452                  * @private
78453                  * @param {Ext.data.NodeInterface} node
78454                  */
78455                 setLastChild : function(node) {
78456                     this.lastChild = node;
78457                 },
78458
78459                 /**
78460                  * Updates general data of this node like isFirst, isLast, depth. This
78461                  * method is internally called after a node is moved. This shouldn't
78462                  * have to be called by the developer unless they are creating custom
78463                  * Tree plugins.
78464                  * @return {Boolean}
78465                  */
78466                 updateInfo: function(silent) {
78467                     var me = this,
78468                         isRoot = me.isRoot(),
78469                         parentNode = me.parentNode,
78470                         isFirst = (!parentNode ? true : parentNode.firstChild == me),
78471                         isLast = (!parentNode ? true : parentNode.lastChild == me),
78472                         depth = 0,
78473                         parent = me,
78474                         children = me.childNodes,
78475                         len = children.length,
78476                         i = 0;
78477
78478                     while (parent.parentNode) {
78479                         ++depth;
78480                         parent = parent.parentNode;
78481                     }
78482
78483                     me.beginEdit();
78484                     me.set({
78485                         isFirst: isFirst,
78486                         isLast: isLast,
78487                         depth: depth,
78488                         index: parentNode ? parentNode.indexOf(me) : 0,
78489                         parentId: parentNode ? parentNode.getId() : null
78490                     });
78491                     me.endEdit(silent);
78492                     if (silent) {
78493                         me.commit();
78494                     }
78495
78496                     for (i = 0; i < len; i++) {
78497                         children[i].updateInfo(silent);
78498                     }
78499                 },
78500
78501                 /**
78502                  * Returns true if this node is the last child of its parent
78503                  * @return {Boolean}
78504                  */
78505                 isLast : function() {
78506                    return this.get('isLast');
78507                 },
78508
78509                 /**
78510                  * Returns true if this node is the first child of its parent
78511                  * @return {Boolean}
78512                  */
78513                 isFirst : function() {
78514                    return this.get('isFirst');
78515                 },
78516
78517                 /**
78518                  * Returns true if this node has one or more child nodes, else false.
78519                  * @return {Boolean}
78520                  */
78521                 hasChildNodes : function() {
78522                     return !this.isLeaf() && this.childNodes.length > 0;
78523                 },
78524
78525                 /**
78526                  * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
78527                  * node attribute is explicitly specified as true, otherwise returns false.
78528                  * @return {Boolean}
78529                  */
78530                 isExpandable : function() {
78531                     var me = this;
78532
78533                     if (me.get('expandable')) {
78534                         return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
78535                     }
78536                     return false;
78537                 },
78538
78539                 /**
78540                  * Inserts node(s) as the last child node of this node.
78541                  *
78542                  * If the node was previously a child node of another parent node, it will be removed from that node first.
78543                  *
78544                  * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append
78545                  * @return {Ext.data.NodeInterface} The appended node if single append, or null if an array was passed
78546                  */
78547                 appendChild : function(node, suppressEvents, suppressNodeUpdate) {
78548                     var me = this,
78549                         i, ln,
78550                         index,
78551                         oldParent,
78552                         ps;
78553
78554                     // if passed an array or multiple args do them one by one
78555                     if (Ext.isArray(node)) {
78556                         for (i = 0, ln = node.length; i < ln; i++) {
78557                             me.appendChild(node[i]);
78558                         }
78559                     } else {
78560                         // Make sure it is a record
78561                         node = me.createNode(node);
78562
78563                         if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
78564                             return false;
78565                         }
78566
78567                         index = me.childNodes.length;
78568                         oldParent = node.parentNode;
78569
78570                         // it's a move, make sure we move it cleanly
78571                         if (oldParent) {
78572                             if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
78573                                 return false;
78574                             }
78575                             oldParent.removeChild(node, null, false, true);
78576                         }
78577
78578                         index = me.childNodes.length;
78579                         if (index === 0) {
78580                             me.setFirstChild(node);
78581                         }
78582
78583                         me.childNodes.push(node);
78584                         node.parentNode = me;
78585                         node.nextSibling = null;
78586
78587                         me.setLastChild(node);
78588
78589                         ps = me.childNodes[index - 1];
78590                         if (ps) {
78591                             node.previousSibling = ps;
78592                             ps.nextSibling = node;
78593                             ps.updateInfo(suppressNodeUpdate);
78594                         } else {
78595                             node.previousSibling = null;
78596                         }
78597
78598                         node.updateInfo(suppressNodeUpdate);
78599
78600                         // As soon as we append a child to this node, we are loaded
78601                         if (!me.isLoaded()) {
78602                             me.set('loaded', true);
78603                         }
78604                         // If this node didnt have any childnodes before, update myself
78605                         else if (me.childNodes.length === 1) {
78606                             me.set('loaded', me.isLoaded());
78607                         }
78608
78609                         if (suppressEvents !== true) {
78610                             me.fireEvent("append", me, node, index);
78611
78612                             if (oldParent) {
78613                                 node.fireEvent("move", node, oldParent, me, index);
78614                             }
78615                         }
78616
78617                         return node;
78618                     }
78619                 },
78620
78621                 /**
78622                  * Returns the bubble target for this node
78623                  * @private
78624                  * @return {Object} The bubble target
78625                  */
78626                 getBubbleTarget: function() {
78627                     return this.parentNode;
78628                 },
78629
78630                 /**
78631                  * Removes a child node from this node.
78632                  * @param {Ext.data.NodeInterface} node The node to remove
78633                  * @param {Boolean} [destroy=false] True to destroy the node upon removal.
78634                  * @return {Ext.data.NodeInterface} The removed node
78635                  */
78636                 removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
78637                     var me = this,
78638                         index = me.indexOf(node);
78639
78640                     if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
78641                         return false;
78642                     }
78643
78644                     // remove it from childNodes collection
78645                     Ext.Array.erase(me.childNodes, index, 1);
78646
78647                     // update child refs
78648                     if (me.firstChild == node) {
78649                         me.setFirstChild(node.nextSibling);
78650                     }
78651                     if (me.lastChild == node) {
78652                         me.setLastChild(node.previousSibling);
78653                     }
78654
78655                     // update siblings
78656                     if (node.previousSibling) {
78657                         node.previousSibling.nextSibling = node.nextSibling;
78658                         node.previousSibling.updateInfo(suppressNodeUpdate);
78659                     }
78660                     if (node.nextSibling) {
78661                         node.nextSibling.previousSibling = node.previousSibling;
78662                         node.nextSibling.updateInfo(suppressNodeUpdate);
78663                     }
78664
78665                     if (suppressEvents !== true) {
78666                         me.fireEvent("remove", me, node);
78667                     }
78668
78669
78670                     // If this node suddenly doesnt have childnodes anymore, update myself
78671                     if (!me.childNodes.length) {
78672                         me.set('loaded', me.isLoaded());
78673                     }
78674
78675                     if (destroy) {
78676                         node.destroy(true);
78677                     } else {
78678                         node.clear();
78679                     }
78680
78681                     return node;
78682                 },
78683
78684                 /**
78685                  * Creates a copy (clone) of this Node.
78686                  * @param {String} [id] A new id, defaults to this Node's id.
78687                  * @param {Boolean} [deep=false] True to recursively copy all child Nodes into the new Node.
78688                  * False to copy without child Nodes.
78689                  * @return {Ext.data.NodeInterface} A copy of this Node.
78690                  */
78691                 copy: function(newId, deep) {
78692                     var me = this,
78693                         result = me.callOverridden(arguments),
78694                         len = me.childNodes ? me.childNodes.length : 0,
78695                         i;
78696
78697                     // Move child nodes across to the copy if required
78698                     if (deep) {
78699                         for (i = 0; i < len; i++) {
78700                             result.appendChild(me.childNodes[i].copy(true));
78701                         }
78702                     }
78703                     return result;
78704                 },
78705
78706                 /**
78707                  * Clears the node.
78708                  * @private
78709                  * @param {Boolean} [destroy=false] True to destroy the node.
78710                  */
78711                 clear : function(destroy) {
78712                     var me = this;
78713
78714                     // clear any references from the node
78715                     me.parentNode = me.previousSibling = me.nextSibling = null;
78716                     if (destroy) {
78717                         me.firstChild = me.lastChild = null;
78718                     }
78719                 },
78720
78721                 /**
78722                  * Destroys the node.
78723                  */
78724                 destroy : function(silent) {
78725                     /*
78726                      * Silent is to be used in a number of cases
78727                      * 1) When setRoot is called.
78728                      * 2) When destroy on the tree is called
78729                      * 3) For destroying child nodes on a node
78730                      */
78731                     var me = this,
78732                         options = me.destroyOptions;
78733
78734                     if (silent === true) {
78735                         me.clear(true);
78736                         Ext.each(me.childNodes, function(n) {
78737                             n.destroy(true);
78738                         });
78739                         me.childNodes = null;
78740                         delete me.destroyOptions;
78741                         me.callOverridden([options]);
78742                     } else {
78743                         me.destroyOptions = silent;
78744                         // overridden method will be called, since remove will end up calling destroy(true);
78745                         me.remove(true);
78746                     }
78747                 },
78748
78749                 /**
78750                  * Inserts the first node before the second node in this nodes childNodes collection.
78751                  * @param {Ext.data.NodeInterface} node The node to insert
78752                  * @param {Ext.data.NodeInterface} refNode The node to insert before (if null the node is appended)
78753                  * @return {Ext.data.NodeInterface} The inserted node
78754                  */
78755                 insertBefore : function(node, refNode, suppressEvents) {
78756                     var me = this,
78757                         index     = me.indexOf(refNode),
78758                         oldParent = node.parentNode,
78759                         refIndex  = index,
78760                         ps;
78761
78762                     if (!refNode) { // like standard Dom, refNode can be null for append
78763                         return me.appendChild(node);
78764                     }
78765
78766                     // nothing to do
78767                     if (node == refNode) {
78768                         return false;
78769                     }
78770
78771                     // Make sure it is a record with the NodeInterface
78772                     node = me.createNode(node);
78773
78774                     if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
78775                         return false;
78776                     }
78777
78778                     // when moving internally, indexes will change after remove
78779                     if (oldParent == me && me.indexOf(node) < index) {
78780                         refIndex--;
78781                     }
78782
78783                     // it's a move, make sure we move it cleanly
78784                     if (oldParent) {
78785                         if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
78786                             return false;
78787                         }
78788                         oldParent.removeChild(node);
78789                     }
78790
78791                     if (refIndex === 0) {
78792                         me.setFirstChild(node);
78793                     }
78794
78795                     Ext.Array.splice(me.childNodes, refIndex, 0, node);
78796                     node.parentNode = me;
78797
78798                     node.nextSibling = refNode;
78799                     refNode.previousSibling = node;
78800
78801                     ps = me.childNodes[refIndex - 1];
78802                     if (ps) {
78803                         node.previousSibling = ps;
78804                         ps.nextSibling = node;
78805                         ps.updateInfo();
78806                     } else {
78807                         node.previousSibling = null;
78808                     }
78809
78810                     node.updateInfo();
78811
78812                     if (!me.isLoaded()) {
78813                         me.set('loaded', true);
78814                     }
78815                     // If this node didnt have any childnodes before, update myself
78816                     else if (me.childNodes.length === 1) {
78817                         me.set('loaded', me.isLoaded());
78818                     }
78819
78820                     if (suppressEvents !== true) {
78821                         me.fireEvent("insert", me, node, refNode);
78822
78823                         if (oldParent) {
78824                             node.fireEvent("move", node, oldParent, me, refIndex, refNode);
78825                         }
78826                     }
78827
78828                     return node;
78829                 },
78830
78831                 /**
78832                  * Insert a node into this node
78833                  * @param {Number} index The zero-based index to insert the node at
78834                  * @param {Ext.data.Model} node The node to insert
78835                  * @return {Ext.data.Model} The record you just inserted
78836                  */
78837                 insertChild: function(index, node) {
78838                     var sibling = this.childNodes[index];
78839                     if (sibling) {
78840                         return this.insertBefore(node, sibling);
78841                     }
78842                     else {
78843                         return this.appendChild(node);
78844                     }
78845                 },
78846
78847                 /**
78848                  * Removes this node from its parent
78849                  * @param {Boolean} [destroy=false] True to destroy the node upon removal.
78850                  * @return {Ext.data.NodeInterface} this
78851                  */
78852                 remove : function(destroy, suppressEvents) {
78853                     var parentNode = this.parentNode;
78854
78855                     if (parentNode) {
78856                         parentNode.removeChild(this, destroy, suppressEvents, true);
78857                     }
78858                     return this;
78859                 },
78860
78861                 /**
78862                  * Removes all child nodes from this node.
78863                  * @param {Boolean} [destroy=false] <True to destroy the node upon removal.
78864                  * @return {Ext.data.NodeInterface} this
78865                  */
78866                 removeAll : function(destroy, suppressEvents) {
78867                     var cn = this.childNodes,
78868                         n;
78869
78870                     while ((n = cn[0])) {
78871                         this.removeChild(n, destroy, suppressEvents);
78872                     }
78873                     return this;
78874                 },
78875
78876                 /**
78877                  * Returns the child node at the specified index.
78878                  * @param {Number} index
78879                  * @return {Ext.data.NodeInterface}
78880                  */
78881                 getChildAt : function(index) {
78882                     return this.childNodes[index];
78883                 },
78884
78885                 /**
78886                  * Replaces one child node in this node with another.
78887                  * @param {Ext.data.NodeInterface} newChild The replacement node
78888                  * @param {Ext.data.NodeInterface} oldChild The node to replace
78889                  * @return {Ext.data.NodeInterface} The replaced node
78890                  */
78891                 replaceChild : function(newChild, oldChild, suppressEvents) {
78892                     var s = oldChild ? oldChild.nextSibling : null;
78893
78894                     this.removeChild(oldChild, suppressEvents);
78895                     this.insertBefore(newChild, s, suppressEvents);
78896                     return oldChild;
78897                 },
78898
78899                 /**
78900                  * Returns the index of a child node
78901                  * @param {Ext.data.NodeInterface} node
78902                  * @return {Number} The index of the node or -1 if it was not found
78903                  */
78904                 indexOf : function(child) {
78905                     return Ext.Array.indexOf(this.childNodes, child);
78906                 },
78907
78908                 /**
78909                  * Gets the hierarchical path from the root of the current node.
78910                  * @param {String} [field] The field to construct the path from. Defaults to the model idProperty.
78911                  * @param {String} [separator="/"] A separator to use.
78912                  * @return {String} The node path
78913                  */
78914                 getPath: function(field, separator) {
78915                     field = field || this.idProperty;
78916                     separator = separator || '/';
78917
78918                     var path = [this.get(field)],
78919                         parent = this.parentNode;
78920
78921                     while (parent) {
78922                         path.unshift(parent.get(field));
78923                         parent = parent.parentNode;
78924                     }
78925                     return separator + path.join(separator);
78926                 },
78927
78928                 /**
78929                  * Returns depth of this node (the root node has a depth of 0)
78930                  * @return {Number}
78931                  */
78932                 getDepth : function() {
78933                     return this.get('depth');
78934                 },
78935
78936                 /**
78937                  * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
78938                  * will be the args provided or the current node. If the function returns false at any point,
78939                  * the bubble is stopped.
78940                  * @param {Function} fn The function to call
78941                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
78942                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78943                  */
78944                 bubble : function(fn, scope, args) {
78945                     var p = this;
78946                     while (p) {
78947                         if (fn.apply(scope || p, args || [p]) === false) {
78948                             break;
78949                         }
78950                         p = p.parentNode;
78951                     }
78952                 },
78953
78954                 cascade: function() {
78955                     if (Ext.isDefined(Ext.global.console)) {
78956                         Ext.global.console.warn('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');
78957                     }
78958                     return this.cascadeBy.apply(this, arguments);
78959                 },
78960
78961                 /**
78962                  * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
78963                  * will be the args provided or the current node. If the function returns false at any point,
78964                  * the cascade is stopped on that branch.
78965                  * @param {Function} fn The function to call
78966                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
78967                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78968                  */
78969                 cascadeBy : function(fn, scope, args) {
78970                     if (fn.apply(scope || this, args || [this]) !== false) {
78971                         var childNodes = this.childNodes,
78972                             length     = childNodes.length,
78973                             i;
78974
78975                         for (i = 0; i < length; i++) {
78976                             childNodes[i].cascadeBy(fn, scope, args);
78977                         }
78978                     }
78979                 },
78980
78981                 /**
78982                  * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
78983                  * will be the args provided or the current node. If the function returns false at any point,
78984                  * the iteration stops.
78985                  * @param {Function} fn The function to call
78986                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node in iteration.
78987                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78988                  */
78989                 eachChild : function(fn, scope, args) {
78990                     var childNodes = this.childNodes,
78991                         length     = childNodes.length,
78992                         i;
78993
78994                     for (i = 0; i < length; i++) {
78995                         if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
78996                             break;
78997                         }
78998                     }
78999                 },
79000
79001                 /**
79002                  * Finds the first child that has the attribute with the specified value.
79003                  * @param {String} attribute The attribute name
79004                  * @param {Object} value The value to search for
79005                  * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
79006                  * @return {Ext.data.NodeInterface} The found child or null if none was found
79007                  */
79008                 findChild : function(attribute, value, deep) {
79009                     return this.findChildBy(function() {
79010                         return this.get(attribute) == value;
79011                     }, null, deep);
79012                 },
79013
79014                 /**
79015                  * Finds the first child by a custom function. The child matches if the function passed returns true.
79016                  * @param {Function} fn A function which must return true if the passed Node is the required Node.
79017                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the Node being tested.
79018                  * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
79019                  * @return {Ext.data.NodeInterface} The found child or null if none was found
79020                  */
79021                 findChildBy : function(fn, scope, deep) {
79022                     var cs = this.childNodes,
79023                         len = cs.length,
79024                         i = 0, n, res;
79025
79026                     for (; i < len; i++) {
79027                         n = cs[i];
79028                         if (fn.call(scope || n, n) === true) {
79029                             return n;
79030                         }
79031                         else if (deep) {
79032                             res = n.findChildBy(fn, scope, deep);
79033                             if (res !== null) {
79034                                 return res;
79035                             }
79036                         }
79037                     }
79038
79039                     return null;
79040                 },
79041
79042                 /**
79043                  * Returns true if this node is an ancestor (at any point) of the passed node.
79044                  * @param {Ext.data.NodeInterface} node
79045                  * @return {Boolean}
79046                  */
79047                 contains : function(node) {
79048                     return node.isAncestor(this);
79049                 },
79050
79051                 /**
79052                  * Returns true if the passed node is an ancestor (at any point) of this node.
79053                  * @param {Ext.data.NodeInterface} node
79054                  * @return {Boolean}
79055                  */
79056                 isAncestor : function(node) {
79057                     var p = this.parentNode;
79058                     while (p) {
79059                         if (p == node) {
79060                             return true;
79061                         }
79062                         p = p.parentNode;
79063                     }
79064                     return false;
79065                 },
79066
79067                 /**
79068                  * Sorts this nodes children using the supplied sort function.
79069                  * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
79070                  * @param {Boolean} [recursive=false] True to apply this sort recursively
79071                  * @param {Boolean} [suppressEvent=false] True to not fire a sort event.
79072                  */
79073                 sort : function(sortFn, recursive, suppressEvent) {
79074                     var cs  = this.childNodes,
79075                         ln = cs.length,
79076                         i, n;
79077
79078                     if (ln > 0) {
79079                         Ext.Array.sort(cs, sortFn);
79080                         for (i = 0; i < ln; i++) {
79081                             n = cs[i];
79082                             n.previousSibling = cs[i-1];
79083                             n.nextSibling = cs[i+1];
79084
79085                             if (i === 0) {
79086                                 this.setFirstChild(n);
79087                                 n.updateInfo();
79088                             }
79089                             if (i == ln - 1) {
79090                                 this.setLastChild(n);
79091                                 n.updateInfo();
79092                             }
79093                             if (recursive && !n.isLeaf()) {
79094                                 n.sort(sortFn, true, true);
79095                             }
79096                         }
79097
79098                         if (suppressEvent !== true) {
79099                             this.fireEvent('sort', this, cs);
79100                         }
79101                     }
79102                 },
79103
79104                 /**
79105                  * Returns true if this node is expaned
79106                  * @return {Boolean}
79107                  */
79108                 isExpanded: function() {
79109                     return this.get('expanded');
79110                 },
79111
79112                 /**
79113                  * Returns true if this node is loaded
79114                  * @return {Boolean}
79115                  */
79116                 isLoaded: function() {
79117                     return this.get('loaded');
79118                 },
79119
79120                 /**
79121                  * Returns true if this node is loading
79122                  * @return {Boolean}
79123                  */
79124                 isLoading: function() {
79125                     return this.get('loading');
79126                 },
79127
79128                 /**
79129                  * Returns true if this node is the root node
79130                  * @return {Boolean}
79131                  */
79132                 isRoot: function() {
79133                     return !this.parentNode;
79134                 },
79135
79136                 /**
79137                  * Returns true if this node is visible
79138                  * @return {Boolean}
79139                  */
79140                 isVisible: function() {
79141                     var parent = this.parentNode;
79142                     while (parent) {
79143                         if (!parent.isExpanded()) {
79144                             return false;
79145                         }
79146                         parent = parent.parentNode;
79147                     }
79148                     return true;
79149                 },
79150
79151                 /**
79152                  * Expand this node.
79153                  * @param {Boolean} [recursive=false] True to recursively expand all the children
79154                  * @param {Function} [callback] The function to execute once the expand completes
79155                  * @param {Object} [scope] The scope to run the callback in
79156                  */
79157                 expand: function(recursive, callback, scope) {
79158                     var me = this;
79159
79160                     // all paths must call the callback (eventually) or things like
79161                     // selectPath fail
79162
79163                     // First we start by checking if this node is a parent
79164                     if (!me.isLeaf()) {
79165                         // If it's loaded, wait until it loads before proceeding
79166                         if (me.isLoading()) {
79167                             me.on('expand', function(){
79168                                 me.expand(recursive, callback, scope);
79169                             }, me, {single: true});
79170                         } else {
79171                             // Now we check if this record is already expanding or expanded
79172                             if (!me.isExpanded()) {
79173                                 // The TreeStore actually listens for the beforeexpand method and checks
79174                                 // whether we have to asynchronously load the children from the server
79175                                 // first. Thats why we pass a callback function to the event that the
79176                                 // store can call once it has loaded and parsed all the children.
79177                                 me.fireEvent('beforeexpand', me, function(){
79178                                     me.set('expanded', true);
79179                                     me.fireEvent('expand', me, me.childNodes, false);
79180
79181                                     // Call the expandChildren method if recursive was set to true
79182                                     if (recursive) {
79183                                         me.expandChildren(true, callback, scope);
79184                                     } else {
79185                                         Ext.callback(callback, scope || me, [me.childNodes]);
79186                                     }
79187                                 }, me);
79188                             } else if (recursive) {
79189                                 // If it is is already expanded but we want to recursively expand then call expandChildren
79190                                 me.expandChildren(true, callback, scope);
79191                             } else {
79192                                 Ext.callback(callback, scope || me, [me.childNodes]);
79193                             }
79194                         }
79195                     } else {
79196                         // If it's not then we fire the callback right away
79197                         Ext.callback(callback, scope || me); // leaf = no childNodes
79198                     }
79199                 },
79200
79201                 /**
79202                  * Expand all the children of this node.
79203                  * @param {Boolean} [recursive=false] True to recursively expand all the children
79204                  * @param {Function} [callback] The function to execute once all the children are expanded
79205                  * @param {Object} [scope] The scope to run the callback in
79206                  */
79207                 expandChildren: function(recursive, callback, scope) {
79208                     var me = this,
79209                         i = 0,
79210                         nodes = me.childNodes,
79211                         ln = nodes.length,
79212                         node,
79213                         expanding = 0;
79214
79215                     for (; i < ln; ++i) {
79216                         node = nodes[i];
79217                         if (!node.isLeaf() && !node.isExpanded()) {
79218                             expanding++;
79219                             nodes[i].expand(recursive, function () {
79220                                 expanding--;
79221                                 if (callback && !expanding) {
79222                                     Ext.callback(callback, scope || me, [me.childNodes]);
79223                                 }
79224                             });
79225                         }
79226                     }
79227
79228                     if (!expanding && callback) {
79229                         Ext.callback(callback, scope || me, [me.childNodes]);                    }
79230                 },
79231
79232                 /**
79233                  * Collapse this node.
79234                  * @param {Boolean} [recursive=false] True to recursively collapse all the children
79235                  * @param {Function} [callback] The function to execute once the collapse completes
79236                  * @param {Object} [scope] The scope to run the callback in
79237                  */
79238                 collapse: function(recursive, callback, scope) {
79239                     var me = this;
79240
79241                     // First we start by checking if this node is a parent
79242                     if (!me.isLeaf()) {
79243                         // Now we check if this record is already collapsing or collapsed
79244                         if (!me.collapsing && me.isExpanded()) {
79245                             me.fireEvent('beforecollapse', me, function() {
79246                                 me.set('expanded', false);
79247                                 me.fireEvent('collapse', me, me.childNodes, false);
79248
79249                                 // Call the collapseChildren method if recursive was set to true
79250                                 if (recursive) {
79251                                     me.collapseChildren(true, callback, scope);
79252                                 }
79253                                 else {
79254                                     Ext.callback(callback, scope || me, [me.childNodes]);
79255                                 }
79256                             }, me);
79257                         }
79258                         // If it is is already collapsed but we want to recursively collapse then call collapseChildren
79259                         else if (recursive) {
79260                             me.collapseChildren(true, callback, scope);
79261                         }
79262                     }
79263                     // If it's not then we fire the callback right away
79264                     else {
79265                         Ext.callback(callback, scope || me, [me.childNodes]);
79266                     }
79267                 },
79268
79269                 /**
79270                  * Collapse all the children of this node.
79271                  * @param {Function} [recursive=false] True to recursively collapse all the children
79272                  * @param {Function} [callback] The function to execute once all the children are collapsed
79273                  * @param {Object} [scope] The scope to run the callback in
79274                  */
79275                 collapseChildren: function(recursive, callback, scope) {
79276                     var me = this,
79277                         i = 0,
79278                         nodes = me.childNodes,
79279                         ln = nodes.length,
79280                         node,
79281                         collapsing = 0;
79282
79283                     for (; i < ln; ++i) {
79284                         node = nodes[i];
79285                         if (!node.isLeaf() && node.isExpanded()) {
79286                             collapsing++;
79287                             nodes[i].collapse(recursive, function () {
79288                                 collapsing--;
79289                                 if (callback && !collapsing) {
79290                                     Ext.callback(callback, scope || me, [me.childNodes]);
79291                                 }
79292                             });
79293                         }
79294                     }
79295
79296                     if (!collapsing && callback) {
79297                         Ext.callback(callback, scope || me, [me.childNodes]);
79298                     }
79299                 }
79300             };
79301         }
79302     }
79303 });
79304 /**
79305  * @class Ext.data.NodeStore
79306  * @extends Ext.data.AbstractStore
79307  * Node Store
79308  * @ignore
79309  */
79310 Ext.define('Ext.data.NodeStore', {
79311     extend: 'Ext.data.Store',
79312     alias: 'store.node',
79313     requires: ['Ext.data.NodeInterface'],
79314     
79315     /**
79316      * @cfg {Ext.data.Model} node
79317      * The Record you want to bind this Store to. Note that
79318      * this record will be decorated with the Ext.data.NodeInterface if this is not the
79319      * case yet.
79320      */
79321     node: null,
79322     
79323     /**
79324      * @cfg {Boolean} recursive
79325      * Set this to true if you want this NodeStore to represent
79326      * all the descendents of the node in its flat data collection. This is useful for
79327      * rendering a tree structure to a DataView and is being used internally by
79328      * the TreeView. Any records that are moved, removed, inserted or appended to the
79329      * node at any depth below the node this store is bound to will be automatically
79330      * updated in this Store's internal flat data structure.
79331      */
79332     recursive: false,
79333     
79334     /** 
79335      * @cfg {Boolean} rootVisible
79336      * False to not include the root node in this Stores collection.
79337      */    
79338     rootVisible: false,
79339     
79340     constructor: function(config) {
79341         var me = this,
79342             node;
79343             
79344         config = config || {};
79345         Ext.apply(me, config);
79346         
79347         if (Ext.isDefined(me.proxy)) {
79348             Ext.Error.raise("A NodeStore cannot be bound to a proxy. Instead bind it to a record " +
79349                             "decorated with the NodeInterface by setting the node config.");
79350         }
79351
79352         config.proxy = {type: 'proxy'};
79353         me.callParent([config]);
79354
79355         me.addEvents('expand', 'collapse', 'beforeexpand', 'beforecollapse');
79356         
79357         node = me.node;
79358         if (node) {
79359             me.node = null;
79360             me.setNode(node);
79361         }
79362     },
79363     
79364     setNode: function(node) {
79365         var me = this;
79366         
79367         if (me.node && me.node != node) {
79368             // We want to unbind our listeners on the old node
79369             me.mun(me.node, {
79370                 expand: me.onNodeExpand,
79371                 collapse: me.onNodeCollapse,
79372                 append: me.onNodeAppend,
79373                 insert: me.onNodeInsert,
79374                 remove: me.onNodeRemove,
79375                 sort: me.onNodeSort,
79376                 scope: me
79377             });
79378             me.node = null;
79379         }
79380         
79381         if (node) {
79382             Ext.data.NodeInterface.decorate(node);
79383             me.removeAll();
79384             if (me.rootVisible) {
79385                 me.add(node);
79386             }
79387             me.mon(node, {
79388                 expand: me.onNodeExpand,
79389                 collapse: me.onNodeCollapse,
79390                 append: me.onNodeAppend,
79391                 insert: me.onNodeInsert,
79392                 remove: me.onNodeRemove,
79393                 sort: me.onNodeSort,
79394                 scope: me
79395             });
79396             me.node = node;
79397             if (node.isExpanded() && node.isLoaded()) {
79398                 me.onNodeExpand(node, node.childNodes, true);
79399             }
79400         }
79401     },
79402     
79403     onNodeSort: function(node, childNodes) {
79404         var me = this;
79405         
79406         if ((me.indexOf(node) !== -1 || (node === me.node && !me.rootVisible) && node.isExpanded())) {
79407             me.onNodeCollapse(node, childNodes, true);
79408             me.onNodeExpand(node, childNodes, true);
79409         }
79410     },
79411     
79412     onNodeExpand: function(parent, records, suppressEvent) {
79413         var me = this,
79414             insertIndex = me.indexOf(parent) + 1,
79415             ln = records ? records.length : 0,
79416             i, record;
79417             
79418         if (!me.recursive && parent !== me.node) {
79419             return;
79420         }
79421         
79422         if (!me.isVisible(parent)) {
79423             return;
79424         }
79425
79426         if (!suppressEvent && me.fireEvent('beforeexpand', parent, records, insertIndex) === false) {
79427             return;
79428         }
79429         
79430         if (ln) {
79431             me.insert(insertIndex, records);
79432             for (i = 0; i < ln; i++) {
79433                 record = records[i];
79434                 if (record.isExpanded()) {
79435                     if (record.isLoaded()) {
79436                         // Take a shortcut                        
79437                         me.onNodeExpand(record, record.childNodes, true);
79438                     }
79439                     else {
79440                         record.set('expanded', false);
79441                         record.expand();
79442                     }
79443                 }
79444             }
79445         }
79446
79447         if (!suppressEvent) {
79448             me.fireEvent('expand', parent, records);
79449         }
79450     },
79451
79452     onNodeCollapse: function(parent, records, suppressEvent) {
79453         var me = this,
79454             ln = records.length,
79455             collapseIndex = me.indexOf(parent) + 1,
79456             i, record;
79457             
79458         if (!me.recursive && parent !== me.node) {
79459             return;
79460         }
79461         
79462         if (!suppressEvent && me.fireEvent('beforecollapse', parent, records, collapseIndex) === false) {
79463             return;
79464         }
79465
79466         for (i = 0; i < ln; i++) {
79467             record = records[i];
79468             me.remove(record);
79469             if (record.isExpanded()) {
79470                 me.onNodeCollapse(record, record.childNodes, true);
79471             }
79472         }
79473         
79474         if (!suppressEvent) {
79475             me.fireEvent('collapse', parent, records, collapseIndex);
79476         }
79477     },
79478     
79479     onNodeAppend: function(parent, node, index) {
79480         var me = this,
79481             refNode, sibling;
79482
79483         if (me.isVisible(node)) {
79484             if (index === 0) {
79485                 refNode = parent;
79486             } else {
79487                 sibling = node.previousSibling;
79488                 while (sibling.isExpanded() && sibling.lastChild) {
79489                     sibling = sibling.lastChild;
79490                 }
79491                 refNode = sibling;
79492             }
79493             me.insert(me.indexOf(refNode) + 1, node);
79494             if (!node.isLeaf() && node.isExpanded()) {
79495                 if (node.isLoaded()) {
79496                     // Take a shortcut                        
79497                     me.onNodeExpand(node, node.childNodes, true);
79498                 }
79499                 else {
79500                     node.set('expanded', false);
79501                     node.expand();
79502                 }
79503             }
79504         } 
79505     },
79506     
79507     onNodeInsert: function(parent, node, refNode) {
79508         var me = this,
79509             index = this.indexOf(refNode);
79510             
79511         if (index != -1 && me.isVisible(node)) {
79512             me.insert(index, node);
79513             if (!node.isLeaf() && node.isExpanded()) {
79514                 if (node.isLoaded()) {
79515                     // Take a shortcut                        
79516                     me.onNodeExpand(node, node.childNodes, true);
79517                 }
79518                 else {
79519                     node.set('expanded', false);
79520                     node.expand();
79521                 }
79522             }
79523         }
79524     },
79525     
79526     onNodeRemove: function(parent, node, index) {
79527         var me = this;
79528         if (me.indexOf(node) != -1) {
79529             if (!node.isLeaf() && node.isExpanded()) {
79530                 me.onNodeCollapse(node, node.childNodes, true);
79531             }            
79532             me.remove(node);
79533         }
79534     },
79535     
79536     isVisible: function(node) {
79537         var parent = node.parentNode;
79538         while (parent) {
79539             if (parent === this.node && !this.rootVisible && parent.isExpanded()) {
79540                 return true;
79541             }
79542             
79543             if (this.indexOf(parent) === -1 || !parent.isExpanded()) {
79544                 return false;
79545             }
79546             
79547             parent = parent.parentNode;
79548         }
79549         return true;
79550     }
79551 });
79552 /**
79553  * @author Ed Spencer
79554  * 
79555  * Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
79556  * All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
79557  * it does not contain any actual logic or perform the request itself.
79558  */
79559 Ext.define('Ext.data.Request', {
79560     /**
79561      * @cfg {String} action
79562      * The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'.
79563      */
79564     action: undefined,
79565     
79566     /**
79567      * @cfg {Object} params
79568      * HTTP request params. The Proxy and its Writer have access to and can modify this object.
79569      */
79570     params: undefined,
79571     
79572     /**
79573      * @cfg {String} method
79574      * The HTTP method to use on this Request. Should be one of 'GET', 'POST', 'PUT' or 'DELETE'.
79575      */
79576     method: 'GET',
79577     
79578     /**
79579      * @cfg {String} url
79580      * The url to access on this Request
79581      */
79582     url: undefined,
79583
79584     /**
79585      * Creates the Request object.
79586      * @param {Object} [config] Config object.
79587      */
79588     constructor: function(config) {
79589         Ext.apply(this, config);
79590     }
79591 });
79592 /**
79593  * @author Don Griffin
79594  *
79595  * This class is a sequential id generator. A simple use of this class would be like so:
79596  *
79597  *     Ext.define('MyApp.data.MyModel', {
79598  *         extend: 'Ext.data.Model',
79599  *         idgen: 'sequential'
79600  *     });
79601  *     // assign id's of 1, 2, 3, etc.
79602  *
79603  * An example of a configured generator would be:
79604  *
79605  *     Ext.define('MyApp.data.MyModel', {
79606  *         extend: 'Ext.data.Model',
79607  *         idgen: {
79608  *             type: 'sequential',
79609  *             prefix: 'ID_',
79610  *             seed: 1000
79611  *         }
79612  *     });
79613  *     // assign id's of ID_1000, ID_1001, ID_1002, etc.
79614  *
79615  */
79616 Ext.define('Ext.data.SequentialIdGenerator', {
79617     extend: 'Ext.data.IdGenerator',
79618     alias: 'idgen.sequential',
79619
79620     constructor: function() {
79621         var me = this;
79622
79623         me.callParent(arguments);
79624
79625         me.parts = [ me.prefix, ''];
79626     },
79627
79628     /**
79629      * @cfg {String} prefix
79630      * The string to place in front of the sequential number for each generated id. The
79631      * default is blank.
79632      */
79633     prefix: '',
79634
79635     /**
79636      * @cfg {Number} seed
79637      * The number at which to start generating sequential id's. The default is 1.
79638      */
79639     seed: 1,
79640
79641     /**
79642      * Generates and returns the next id.
79643      * @return {String} The next id.
79644      */
79645     generate: function () {
79646         var me = this,
79647             parts = me.parts;
79648
79649         parts[1] = me.seed++;
79650         return parts.join('');
79651     }
79652 });
79653
79654 /**
79655  * @class Ext.data.Tree
79656  *
79657  * This class is used as a container for a series of nodes. The nodes themselves maintain
79658  * the relationship between parent/child. The tree itself acts as a manager. It gives functionality
79659  * to retrieve a node by its identifier: {@link #getNodeById}.
79660  *
79661  * The tree also relays events from any of it's child nodes, allowing them to be handled in a
79662  * centralized fashion. In general this class is not used directly, rather used internally
79663  * by other parts of the framework.
79664  *
79665  */
79666 Ext.define('Ext.data.Tree', {
79667     alias: 'data.tree',
79668
79669     mixins: {
79670         observable: "Ext.util.Observable"
79671     },
79672
79673     /**
79674      * @property {Ext.data.NodeInterface}
79675      * The root node for this tree
79676      */
79677     root: null,
79678
79679     /**
79680      * Creates new Tree object.
79681      * @param {Ext.data.NodeInterface} root (optional) The root node
79682      */
79683     constructor: function(root) {
79684         var me = this;
79685
79686         
79687
79688         me.mixins.observable.constructor.call(me);
79689
79690         if (root) {
79691             me.setRootNode(root);
79692         }
79693     },
79694
79695     /**
79696      * Returns the root node for this tree.
79697      * @return {Ext.data.NodeInterface}
79698      */
79699     getRootNode : function() {
79700         return this.root;
79701     },
79702
79703     /**
79704      * Sets the root node for this tree.
79705      * @param {Ext.data.NodeInterface} node
79706      * @return {Ext.data.NodeInterface} The root node
79707      */
79708     setRootNode : function(node) {
79709         var me = this;
79710
79711         me.root = node;
79712         Ext.data.NodeInterface.decorate(node);
79713
79714         if (me.fireEvent('beforeappend', null, node) !== false) {
79715             node.set('root', true);
79716             node.updateInfo();
79717
79718             me.relayEvents(node, [
79719                 /**
79720                  * @event append
79721                  * @alias Ext.data.NodeInterface#append
79722                  */
79723                 "append",
79724
79725                 /**
79726                  * @event remove
79727                  * @alias Ext.data.NodeInterface#remove
79728                  */
79729                 "remove",
79730
79731                 /**
79732                  * @event move
79733                  * @alias Ext.data.NodeInterface#move
79734                  */
79735                 "move",
79736
79737                 /**
79738                  * @event insert
79739                  * @alias Ext.data.NodeInterface#insert
79740                  */
79741                 "insert",
79742
79743                 /**
79744                  * @event beforeappend
79745                  * @alias Ext.data.NodeInterface#beforeappend
79746                  */
79747                 "beforeappend",
79748
79749                 /**
79750                  * @event beforeremove
79751                  * @alias Ext.data.NodeInterface#beforeremove
79752                  */
79753                 "beforeremove",
79754
79755                 /**
79756                  * @event beforemove
79757                  * @alias Ext.data.NodeInterface#beforemove
79758                  */
79759                 "beforemove",
79760
79761                 /**
79762                  * @event beforeinsert
79763                  * @alias Ext.data.NodeInterface#beforeinsert
79764                  */
79765                 "beforeinsert",
79766
79767                  /**
79768                   * @event expand
79769                   * @alias Ext.data.NodeInterface#expand
79770                   */
79771                  "expand",
79772
79773                  /**
79774                   * @event collapse
79775                   * @alias Ext.data.NodeInterface#collapse
79776                   */
79777                  "collapse",
79778
79779                  /**
79780                   * @event beforeexpand
79781                   * @alias Ext.data.NodeInterface#beforeexpand
79782                   */
79783                  "beforeexpand",
79784
79785                  /**
79786                   * @event beforecollapse
79787                   * @alias Ext.data.NodeInterface#beforecollapse
79788                   */
79789                  "beforecollapse" ,
79790
79791                  /**
79792                   * @event rootchange
79793                   * Fires whenever the root node is changed in the tree.
79794                   * @param {Ext.data.Model} root The new root
79795                   */
79796                  "rootchange"
79797             ]);
79798
79799             node.on({
79800                 scope: me,
79801                 insert: me.onNodeInsert,
79802                 append: me.onNodeAppend,
79803                 remove: me.onNodeRemove
79804             });
79805
79806             me.nodeHash = {};
79807             me.registerNode(node);
79808             me.fireEvent('append', null, node);
79809             me.fireEvent('rootchange', node);
79810         }
79811
79812         return node;
79813     },
79814
79815     /**
79816      * Flattens all the nodes in the tree into an array.
79817      * @private
79818      * @return {Ext.data.NodeInterface[]} The flattened nodes.
79819      */
79820     flatten: function(){
79821         var nodes = [],
79822             hash = this.nodeHash,
79823             key;
79824
79825         for (key in hash) {
79826             if (hash.hasOwnProperty(key)) {
79827                 nodes.push(hash[key]);
79828             }
79829         }
79830         return nodes;
79831     },
79832
79833     /**
79834      * Fired when a node is inserted into the root or one of it's children
79835      * @private
79836      * @param {Ext.data.NodeInterface} parent The parent node
79837      * @param {Ext.data.NodeInterface} node The inserted node
79838      */
79839     onNodeInsert: function(parent, node) {
79840         this.registerNode(node, true);
79841     },
79842
79843     /**
79844      * Fired when a node is appended into the root or one of it's children
79845      * @private
79846      * @param {Ext.data.NodeInterface} parent The parent node
79847      * @param {Ext.data.NodeInterface} node The appended node
79848      */
79849     onNodeAppend: function(parent, node) {
79850         this.registerNode(node, true);
79851     },
79852
79853     /**
79854      * Fired when a node is removed from the root or one of it's children
79855      * @private
79856      * @param {Ext.data.NodeInterface} parent The parent node
79857      * @param {Ext.data.NodeInterface} node The removed node
79858      */
79859     onNodeRemove: function(parent, node) {
79860         this.unregisterNode(node, true);
79861     },
79862
79863     /**
79864      * Gets a node in this tree by its id.
79865      * @param {String} id
79866      * @return {Ext.data.NodeInterface} The match node.
79867      */
79868     getNodeById : function(id) {
79869         return this.nodeHash[id];
79870     },
79871
79872     /**
79873      * Registers a node with the tree
79874      * @private
79875      * @param {Ext.data.NodeInterface} The node to register
79876      * @param {Boolean} [includeChildren] True to unregister any child nodes
79877      */
79878     registerNode : function(node, includeChildren) {
79879         this.nodeHash[node.getId() || node.internalId] = node;
79880         if (includeChildren === true) {
79881             node.eachChild(function(child){
79882                 this.registerNode(child, true);
79883             }, this);
79884         }
79885     },
79886
79887     /**
79888      * Unregisters a node with the tree
79889      * @private
79890      * @param {Ext.data.NodeInterface} The node to unregister
79891      * @param {Boolean} [includeChildren] True to unregister any child nodes
79892      */
79893     unregisterNode : function(node, includeChildren) {
79894         delete this.nodeHash[node.getId() || node.internalId];
79895         if (includeChildren === true) {
79896             node.eachChild(function(child){
79897                 this.unregisterNode(child, true);
79898             }, this);
79899         }
79900     },
79901
79902     /**
79903      * Sorts this tree
79904      * @private
79905      * @param {Function} sorterFn The function to use for sorting
79906      * @param {Boolean} recursive True to perform recursive sorting
79907      */
79908     sort: function(sorterFn, recursive) {
79909         this.getRootNode().sort(sorterFn, recursive);
79910     },
79911
79912      /**
79913      * Filters this tree
79914      * @private
79915      * @param {Function} sorterFn The function to use for filtering
79916      * @param {Boolean} recursive True to perform recursive filtering
79917      */
79918     filter: function(filters, recursive) {
79919         this.getRootNode().filter(filters, recursive);
79920     }
79921 });
79922 /**
79923  * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}.
79924  * It provides convenience methods for loading nodes, as well as the ability to use
79925  * the hierarchical tree structure combined with a store. This class is generally used
79926  * in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
79927  * the Tree for convenience.
79928  *
79929  * # Using Models
79930  *
79931  * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
79932  * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
79933  * in the {@link Ext.data.NodeInterface} documentation.
79934  *
79935  * # Reading Nested Data
79936  *
79937  * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
79938  * so the reader can find nested data for each node. If a root is not specified, it will default to
79939  * 'children'.
79940  */
79941 Ext.define('Ext.data.TreeStore', {
79942     extend: 'Ext.data.AbstractStore',
79943     alias: 'store.tree',
79944     requires: ['Ext.data.Tree', 'Ext.data.NodeInterface', 'Ext.data.NodeStore'],
79945
79946     /**
79947      * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
79948      * The root node for this store. For example:
79949      *
79950      *     root: {
79951      *         expanded: true,
79952      *         text: "My Root",
79953      *         children: [
79954      *             { text: "Child 1", leaf: true },
79955      *             { text: "Child 2", expanded: true, children: [
79956      *                 { text: "GrandChild", leaf: true }
79957      *             ] }
79958      *         ]
79959      *     }
79960      *
79961      * Setting the `root` config option is the same as calling {@link #setRootNode}.
79962      */
79963
79964     /**
79965      * @cfg {Boolean} clearOnLoad
79966      * Remove previously existing child nodes before loading. Default to true.
79967      */
79968     clearOnLoad : true,
79969
79970     /**
79971      * @cfg {String} nodeParam
79972      * The name of the parameter sent to the server which contains the identifier of the node.
79973      * Defaults to 'node'.
79974      */
79975     nodeParam: 'node',
79976
79977     /**
79978      * @cfg {String} defaultRootId
79979      * The default root id. Defaults to 'root'
79980      */
79981     defaultRootId: 'root',
79982
79983     /**
79984      * @cfg {String} defaultRootProperty
79985      * The root property to specify on the reader if one is not explicitly defined.
79986      */
79987     defaultRootProperty: 'children',
79988
79989     /**
79990      * @cfg {Boolean} folderSort
79991      * Set to true to automatically prepend a leaf sorter. Defaults to `undefined`.
79992      */
79993     folderSort: false,
79994
79995     constructor: function(config) {
79996         var me = this,
79997             root,
79998             fields;
79999
80000         config = Ext.apply({}, config);
80001
80002         /**
80003          * If we have no fields declare for the store, add some defaults.
80004          * These will be ignored if a model is explicitly specified.
80005          */
80006         fields = config.fields || me.fields;
80007         if (!fields) {
80008             config.fields = [{name: 'text', type: 'string'}];
80009         }
80010
80011         me.callParent([config]);
80012
80013         // We create our data tree.
80014         me.tree = Ext.create('Ext.data.Tree');
80015
80016         me.relayEvents(me.tree, [
80017             /**
80018              * @event append
80019              * @alias Ext.data.Tree#append
80020              */
80021             "append",
80022
80023             /**
80024              * @event remove
80025              * @alias Ext.data.Tree#remove
80026              */
80027             "remove",
80028
80029             /**
80030              * @event move
80031              * @alias Ext.data.Tree#move
80032              */
80033             "move",
80034
80035             /**
80036              * @event insert
80037              * @alias Ext.data.Tree#insert
80038              */
80039             "insert",
80040
80041             /**
80042              * @event beforeappend
80043              * @alias Ext.data.Tree#beforeappend
80044              */
80045             "beforeappend",
80046
80047             /**
80048              * @event beforeremove
80049              * @alias Ext.data.Tree#beforeremove
80050              */
80051             "beforeremove",
80052
80053             /**
80054              * @event beforemove
80055              * @alias Ext.data.Tree#beforemove
80056              */
80057             "beforemove",
80058
80059             /**
80060              * @event beforeinsert
80061              * @alias Ext.data.Tree#beforeinsert
80062              */
80063             "beforeinsert",
80064
80065              /**
80066               * @event expand
80067               * @alias Ext.data.Tree#expand
80068               */
80069              "expand",
80070
80071              /**
80072               * @event collapse
80073               * @alias Ext.data.Tree#collapse
80074               */
80075              "collapse",
80076
80077              /**
80078               * @event beforeexpand
80079               * @alias Ext.data.Tree#beforeexpand
80080               */
80081              "beforeexpand",
80082
80083              /**
80084               * @event beforecollapse
80085               * @alias Ext.data.Tree#beforecollapse
80086               */
80087              "beforecollapse",
80088
80089              /**
80090               * @event rootchange
80091               * @alias Ext.data.Tree#rootchange
80092               */
80093              "rootchange"
80094         ]);
80095
80096         me.tree.on({
80097             scope: me,
80098             remove: me.onNodeRemove,
80099             // this event must follow the relay to beforeitemexpand to allow users to
80100             // cancel the expand:
80101             beforeexpand: me.onBeforeNodeExpand,
80102             beforecollapse: me.onBeforeNodeCollapse,
80103             append: me.onNodeAdded,
80104             insert: me.onNodeAdded
80105         });
80106
80107         me.onBeforeSort();
80108
80109         root = me.root;
80110         if (root) {
80111             delete me.root;
80112             me.setRootNode(root);
80113         }
80114
80115         me.addEvents(
80116             /**
80117              * @event sort
80118              * Fires when this TreeStore is sorted.
80119              * @param {Ext.data.NodeInterface} node The node that is sorted.
80120              */
80121             'sort'
80122         );
80123
80124         if (Ext.isDefined(me.nodeParameter)) {
80125             if (Ext.isDefined(Ext.global.console)) {
80126                 Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');
80127             }
80128             me.nodeParam = me.nodeParameter;
80129             delete me.nodeParameter;
80130         }
80131     },
80132
80133     // inherit docs
80134     setProxy: function(proxy) {
80135         var reader,
80136             needsRoot;
80137
80138         if (proxy instanceof Ext.data.proxy.Proxy) {
80139             // proxy instance, check if a root was set
80140             needsRoot = Ext.isEmpty(proxy.getReader().root);
80141         } else if (Ext.isString(proxy)) {
80142             // string type, means a reader can't be set
80143             needsRoot = true;
80144         } else {
80145             // object, check if a reader and a root were specified.
80146             reader = proxy.reader;
80147             needsRoot = !(reader && !Ext.isEmpty(reader.root));
80148         }
80149         proxy = this.callParent(arguments);
80150         if (needsRoot) {
80151             reader = proxy.getReader();
80152             reader.root = this.defaultRootProperty;
80153             // force rebuild
80154             reader.buildExtractors(true);
80155         }
80156     },
80157
80158     // inherit docs
80159     onBeforeSort: function() {
80160         if (this.folderSort) {
80161             this.sort({
80162                 property: 'leaf',
80163                 direction: 'ASC'
80164             }, 'prepend', false);
80165         }
80166     },
80167
80168     /**
80169      * Called before a node is expanded.
80170      * @private
80171      * @param {Ext.data.NodeInterface} node The node being expanded.
80172      * @param {Function} callback The function to run after the expand finishes
80173      * @param {Object} scope The scope in which to run the callback function
80174      */
80175     onBeforeNodeExpand: function(node, callback, scope) {
80176         if (node.isLoaded()) {
80177             Ext.callback(callback, scope || node, [node.childNodes]);
80178         }
80179         else if (node.isLoading()) {
80180             this.on('load', function() {
80181                 Ext.callback(callback, scope || node, [node.childNodes]);
80182             }, this, {single: true});
80183         }
80184         else {
80185             this.read({
80186                 node: node,
80187                 callback: function() {
80188                     Ext.callback(callback, scope || node, [node.childNodes]);
80189                 }
80190             });
80191         }
80192     },
80193
80194     //inherit docs
80195     getNewRecords: function() {
80196         return Ext.Array.filter(this.tree.flatten(), this.filterNew);
80197     },
80198
80199     //inherit docs
80200     getUpdatedRecords: function() {
80201         return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
80202     },
80203
80204     /**
80205      * Called before a node is collapsed.
80206      * @private
80207      * @param {Ext.data.NodeInterface} node The node being collapsed.
80208      * @param {Function} callback The function to run after the collapse finishes
80209      * @param {Object} scope The scope in which to run the callback function
80210      */
80211     onBeforeNodeCollapse: function(node, callback, scope) {
80212         callback.call(scope || node, node.childNodes);
80213     },
80214
80215     onNodeRemove: function(parent, node) {
80216         var removed = this.removed;
80217
80218         if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {
80219             removed.push(node);
80220         }
80221     },
80222
80223     onNodeAdded: function(parent, node) {
80224         var proxy = this.getProxy(),
80225             reader = proxy.getReader(),
80226             data = node.raw || node.data,
80227             dataRoot, children;
80228
80229         Ext.Array.remove(this.removed, node);
80230
80231         if (!node.isLeaf() && !node.isLoaded()) {
80232             dataRoot = reader.getRoot(data);
80233             if (dataRoot) {
80234                 this.fillNode(node, reader.extractData(dataRoot));
80235                 delete data[reader.root];
80236             }
80237         }
80238     },
80239
80240     /**
80241      * Sets the root node for this store.  See also the {@link #root} config option.
80242      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
80243      * @return {Ext.data.NodeInterface} The new root
80244      */
80245     setRootNode: function(root) {
80246         var me = this;
80247
80248         root = root || {};
80249         if (!root.isNode) {
80250             // create a default rootNode and create internal data struct.
80251             Ext.applyIf(root, {
80252                 id: me.defaultRootId,
80253                 text: 'Root',
80254                 allowDrag: false
80255             });
80256             root = Ext.ModelManager.create(root, me.model);
80257         }
80258         Ext.data.NodeInterface.decorate(root);
80259
80260         // Because we have decorated the model with new fields,
80261         // we need to build new extactor functions on the reader.
80262         me.getProxy().getReader().buildExtractors(true);
80263
80264         // When we add the root to the tree, it will automaticaly get the NodeInterface
80265         me.tree.setRootNode(root);
80266
80267         // If the user has set expanded: true on the root, we want to call the expand function
80268         if (!root.isLoaded() && (me.autoLoad === true || root.isExpanded())) {
80269             me.load({
80270                 node: root
80271             });
80272         }
80273
80274         return root;
80275     },
80276
80277     /**
80278      * Returns the root node for this tree.
80279      * @return {Ext.data.NodeInterface}
80280      */
80281     getRootNode: function() {
80282         return this.tree.getRootNode();
80283     },
80284
80285     /**
80286      * Returns the record node by id
80287      * @return {Ext.data.NodeInterface}
80288      */
80289     getNodeById: function(id) {
80290         return this.tree.getNodeById(id);
80291     },
80292
80293     /**
80294      * Loads the Store using its configured {@link #proxy}.
80295      * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
80296      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
80297      * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
80298      * default to the root node.
80299      */
80300     load: function(options) {
80301         options = options || {};
80302         options.params = options.params || {};
80303
80304         var me = this,
80305             node = options.node || me.tree.getRootNode(),
80306             root;
80307
80308         // If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
80309         // create one for them.
80310         if (!node) {
80311             node = me.setRootNode({
80312                 expanded: true
80313             });
80314         }
80315
80316         if (me.clearOnLoad) {
80317             node.removeAll(true);
80318         }
80319
80320         Ext.applyIf(options, {
80321             node: node
80322         });
80323         options.params[me.nodeParam] = node ? node.getId() : 'root';
80324
80325         if (node) {
80326             node.set('loading', true);
80327         }
80328
80329         return me.callParent([options]);
80330     },
80331
80332
80333     /**
80334      * Fills a node with a series of child records.
80335      * @private
80336      * @param {Ext.data.NodeInterface} node The node to fill
80337      * @param {Ext.data.Model[]} records The records to add
80338      */
80339     fillNode: function(node, records) {
80340         var me = this,
80341             ln = records ? records.length : 0,
80342             i = 0, sortCollection;
80343
80344         if (ln && me.sortOnLoad && !me.remoteSort && me.sorters && me.sorters.items) {
80345             sortCollection = Ext.create('Ext.util.MixedCollection');
80346             sortCollection.addAll(records);
80347             sortCollection.sort(me.sorters.items);
80348             records = sortCollection.items;
80349         }
80350
80351         node.set('loaded', true);
80352         for (; i < ln; i++) {
80353             node.appendChild(records[i], undefined, true);
80354         }
80355
80356         return records;
80357     },
80358
80359     // inherit docs
80360     onProxyLoad: function(operation) {
80361         var me = this,
80362             successful = operation.wasSuccessful(),
80363             records = operation.getRecords(),
80364             node = operation.node;
80365
80366         me.loading = false;
80367         node.set('loading', false);
80368         if (successful) {
80369             records = me.fillNode(node, records);
80370         }
80371         // The load event has an extra node parameter
80372         // (differing from the load event described in AbstractStore)
80373         /**
80374          * @event load
80375          * Fires whenever the store reads data from a remote data source.
80376          * @param {Ext.data.TreeStore} this
80377          * @param {Ext.data.NodeInterface} node The node that was loaded.
80378          * @param {Ext.data.Model[]} records An array of records.
80379          * @param {Boolean} successful True if the operation was successful.
80380          */
80381         // deprecate read?
80382         me.fireEvent('read', me, operation.node, records, successful);
80383         me.fireEvent('load', me, operation.node, records, successful);
80384         //this is a callback that would have been passed to the 'read' function and is optional
80385         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
80386     },
80387
80388     /**
80389      * Creates any new records when a write is returned from the server.
80390      * @private
80391      * @param {Ext.data.Model[]} records The array of new records
80392      * @param {Ext.data.Operation} operation The operation that just completed
80393      * @param {Boolean} success True if the operation was successful
80394      */
80395     onCreateRecords: function(records, operation, success) {
80396         if (success) {
80397             var i = 0,
80398                 length = records.length,
80399                 originalRecords = operation.records,
80400                 parentNode,
80401                 record,
80402                 original,
80403                 index;
80404
80405             /*
80406              * Loop over each record returned from the server. Assume they are
80407              * returned in order of how they were sent. If we find a matching
80408              * record, replace it with the newly created one.
80409              */
80410             for (; i < length; ++i) {
80411                 record = records[i];
80412                 original = originalRecords[i];
80413                 if (original) {
80414                     parentNode = original.parentNode;
80415                     if (parentNode) {
80416                         // prevent being added to the removed cache
80417                         original.isReplace = true;
80418                         parentNode.replaceChild(record, original);
80419                         delete original.isReplace;
80420                     }
80421                     record.phantom = false;
80422                 }
80423             }
80424         }
80425     },
80426
80427     /**
80428      * Updates any records when a write is returned from the server.
80429      * @private
80430      * @param {Ext.data.Model[]} records The array of updated records
80431      * @param {Ext.data.Operation} operation The operation that just completed
80432      * @param {Boolean} success True if the operation was successful
80433      */
80434     onUpdateRecords: function(records, operation, success){
80435         if (success) {
80436             var me = this,
80437                 i = 0,
80438                 length = records.length,
80439                 data = me.data,
80440                 original,
80441                 parentNode,
80442                 record;
80443
80444             for (; i < length; ++i) {
80445                 record = records[i];
80446                 original = me.tree.getNodeById(record.getId());
80447                 parentNode = original.parentNode;
80448                 if (parentNode) {
80449                     // prevent being added to the removed cache
80450                     original.isReplace = true;
80451                     parentNode.replaceChild(record, original);
80452                     original.isReplace = false;
80453                 }
80454             }
80455         }
80456     },
80457
80458     /**
80459      * Removes any records when a write is returned from the server.
80460      * @private
80461      * @param {Ext.data.Model[]} records The array of removed records
80462      * @param {Ext.data.Operation} operation The operation that just completed
80463      * @param {Boolean} success True if the operation was successful
80464      */
80465     onDestroyRecords: function(records, operation, success){
80466         if (success) {
80467             this.removed = [];
80468         }
80469     },
80470
80471     // inherit docs
80472     removeAll: function() {
80473         this.getRootNode().destroy(true);
80474         this.fireEvent('clear', this);
80475     },
80476
80477     // inherit docs
80478     doSort: function(sorterFn) {
80479         var me = this;
80480         if (me.remoteSort) {
80481             //the load function will pick up the new sorters and request the sorted data from the proxy
80482             me.load();
80483         } else {
80484             me.tree.sort(sorterFn, true);
80485             me.fireEvent('datachanged', me);
80486         }
80487         me.fireEvent('sort', me);
80488     }
80489 });
80490
80491 /**
80492  * @extend Ext.data.IdGenerator
80493  * @author Don Griffin
80494  *
80495  * This class generates UUID's according to RFC 4122. This class has a default id property.
80496  * This means that a single instance is shared unless the id property is overridden. Thus,
80497  * two {@link Ext.data.Model} instances configured like the following share one generator:
80498  *
80499  *     Ext.define('MyApp.data.MyModelX', {
80500  *         extend: 'Ext.data.Model',
80501  *         idgen: 'uuid'
80502  *     });
80503  *
80504  *     Ext.define('MyApp.data.MyModelY', {
80505  *         extend: 'Ext.data.Model',
80506  *         idgen: 'uuid'
80507  *     });
80508  *
80509  * This allows all models using this class to share a commonly configured instance.
80510  *
80511  * # Using Version 1 ("Sequential") UUID's
80512  *
80513  * If a server can provide a proper timestamp and a "cryptographic quality random number"
80514  * (as described in RFC 4122), the shared instance can be configured as follows:
80515  *
80516  *     Ext.data.IdGenerator.get('uuid').reconfigure({
80517  *         version: 1,
80518  *         clockSeq: clock, // 14 random bits
80519  *         salt: salt,      // 48 secure random bits (the Node field)
80520  *         timestamp: ts    // timestamp per Section 4.1.4
80521  *     });
80522  *
80523  *     // or these values can be split into 32-bit chunks:
80524  *
80525  *     Ext.data.IdGenerator.get('uuid').reconfigure({
80526  *         version: 1,
80527  *         clockSeq: clock,
80528  *         salt: { lo: saltLow32, hi: saltHigh32 },
80529  *         timestamp: { lo: timestampLow32, hi: timestamptHigh32 }
80530  *     });
80531  *
80532  * This approach improves the generator's uniqueness by providing a valid timestamp and
80533  * higher quality random data. Version 1 UUID's should not be used unless this information
80534  * can be provided by a server and care should be taken to avoid caching of this data.
80535  *
80536  * See http://www.ietf.org/rfc/rfc4122.txt for details.
80537  */
80538 Ext.define('Ext.data.UuidGenerator', function () {
80539     var twoPow14 = Math.pow(2, 14),
80540         twoPow16 = Math.pow(2, 16),
80541         twoPow28 = Math.pow(2, 28),
80542         twoPow32 = Math.pow(2, 32);
80543
80544     function toHex (value, length) {
80545         var ret = value.toString(16);
80546         if (ret.length > length) {
80547             ret = ret.substring(ret.length - length); // right-most digits
80548         } else if (ret.length < length) {
80549             ret = Ext.String.leftPad(ret, length, '0');
80550         }
80551         return ret;
80552     }
80553
80554     function rand (lo, hi) {
80555         var v = Math.random() * (hi - lo + 1);
80556         return Math.floor(v) + lo;
80557     }
80558
80559     function split (bignum) {
80560         if (typeof(bignum) == 'number') {
80561             var hi = Math.floor(bignum / twoPow32);
80562             return {
80563                 lo: Math.floor(bignum - hi * twoPow32),
80564                 hi: hi
80565             };
80566         }
80567         return bignum;
80568     }
80569
80570     return {
80571         extend: 'Ext.data.IdGenerator',
80572
80573         alias: 'idgen.uuid',
80574
80575         id: 'uuid', // shared by default
80576
80577         /**
80578          * @property {Number/Object} salt
80579          * When created, this value is a 48-bit number. For computation, this value is split
80580          * into 32-bit parts and stored in an object with `hi` and `lo` properties.
80581          */
80582
80583         /**
80584          * @property {Number/Object} timestamp
80585          * When created, this value is a 60-bit number. For computation, this value is split
80586          * into 32-bit parts and stored in an object with `hi` and `lo` properties.
80587          */
80588
80589         /**
80590          * @cfg {Number} version
80591          * The Version of UUID. Supported values are:
80592          *
80593          *  * 1 : Time-based, "sequential" UUID.
80594          *  * 4 : Pseudo-random UUID.
80595          *
80596          * The default is 4.
80597          */
80598         version: 4,
80599
80600         constructor: function() {
80601             var me = this;
80602
80603             me.callParent(arguments);
80604
80605             me.parts = [];
80606             me.init();
80607         },
80608
80609         generate: function () {
80610             var me = this,
80611                 parts = me.parts,
80612                 ts = me.timestamp;
80613
80614             /*
80615                The magic decoder ring (derived from RFC 4122 Section 4.2.2):
80616
80617                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80618                |                          time_low                             |
80619                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80620                |           time_mid            |  ver  |        time_hi        |
80621                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80622                |res|  clock_hi |   clock_low   |    salt 0   |M|     salt 1    |
80623                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80624                |                         salt (2-5)                            |
80625                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80626
80627                          time_mid      clock_hi (low 6 bits)
80628                 time_low     | time_hi |clock_lo
80629                     |        |     |   || salt[0]
80630                     |        |     |   ||   | salt[1..5]
80631                     v        v     v   vv   v v
80632                     0badf00d-aced-1def-b123-dfad0badbeef
80633                                   ^    ^     ^
80634                             version    |     multicast (low bit)
80635                                        |
80636                                     reserved (upper 2 bits)
80637             */
80638             parts[0] = toHex(ts.lo, 8);
80639             parts[1] = toHex(ts.hi & 0xFFFF, 4);
80640             parts[2] = toHex(((ts.hi >>> 16) & 0xFFF) | (me.version << 12), 4);
80641             parts[3] = toHex(0x80 | ((me.clockSeq >>> 8) & 0x3F), 2) +
80642                        toHex(me.clockSeq & 0xFF, 2);
80643             parts[4] = toHex(me.salt.hi, 4) + toHex(me.salt.lo, 8);
80644
80645             if (me.version == 4) {
80646                 me.init(); // just regenerate all the random values...
80647             } else {
80648                 // sequentially increment the timestamp...
80649                 ++ts.lo;
80650                 if (ts.lo >= twoPow32) { // if (overflow)
80651                     ts.lo = 0;
80652                     ++ts.hi;
80653                 }
80654             }
80655
80656             return parts.join('-').toLowerCase();
80657         },
80658
80659         getRecId: function (rec) {
80660             return rec.getId();
80661         },
80662
80663         /**
80664          * @private
80665          */
80666         init: function () {
80667             var me = this,
80668                 salt, time;
80669
80670             if (me.version == 4) {
80671                 // See RFC 4122 (Secion 4.4)
80672                 //   o  If the state was unavailable (e.g., non-existent or corrupted),
80673                 //      or the saved node ID is different than the current node ID,
80674                 //      generate a random clock sequence value.
80675                 me.clockSeq = rand(0, twoPow14-1);
80676
80677                 // we run this on every id generation...
80678                 salt = me.salt || (me.salt = {});
80679                 time = me.timestamp || (me.timestamp = {});
80680
80681                 // See RFC 4122 (Secion 4.4)
80682                 salt.lo = rand(0, twoPow32-1);
80683                 salt.hi = rand(0, twoPow16-1);
80684                 time.lo = rand(0, twoPow32-1);
80685                 time.hi = rand(0, twoPow28-1);
80686             } else {
80687                 // this is run only once per-instance
80688                 me.salt = split(me.salt);
80689                 me.timestamp = split(me.timestamp);
80690
80691                 // Set multicast bit: "the least significant bit of the first octet of the
80692                 // node ID" (nodeId = salt for this implementation):
80693                 me.salt.hi |= 0x100;
80694             }
80695         },
80696
80697         /**
80698          * Reconfigures this generator given new config properties.
80699          */
80700         reconfigure: function (config) {
80701             Ext.apply(this, config);
80702             this.init();
80703         }
80704     };
80705 }());
80706
80707 /**
80708  * @author Ed Spencer
80709  * @class Ext.data.XmlStore
80710  * @extends Ext.data.Store
80711  * @private
80712  * @ignore
80713  * <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.
80714  * A XmlStore will be automatically configured with a {@link Ext.data.reader.Xml}.</p>
80715  * <p>A store configuration would be something like:<pre><code>
80716 var store = new Ext.data.XmlStore({
80717     // store configs
80718     autoDestroy: true,
80719     storeId: 'myStore',
80720     url: 'sheldon.xml', // automatically configures a HttpProxy
80721     // reader configs
80722     record: 'Item', // records will have an "Item" tag
80723     idPath: 'ASIN',
80724     totalRecords: '@TotalResults'
80725     fields: [
80726         // set up the fields mapping into the xml doc
80727         // The first needs mapping, the others are very basic
80728         {name: 'Author', mapping: 'ItemAttributes > Author'},
80729         'Title', 'Manufacturer', 'ProductGroup'
80730     ]
80731 });
80732  * </code></pre></p>
80733  * <p>This store is configured to consume a returned object of the form:<pre><code>
80734 &#60?xml version="1.0" encoding="UTF-8"?>
80735 &#60ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">
80736     &#60Items>
80737         &#60Request>
80738             &#60IsValid>True&#60/IsValid>
80739             &#60ItemSearchRequest>
80740                 &#60Author>Sidney Sheldon&#60/Author>
80741                 &#60SearchIndex>Books&#60/SearchIndex>
80742             &#60/ItemSearchRequest>
80743         &#60/Request>
80744         &#60TotalResults>203&#60/TotalResults>
80745         &#60TotalPages>21&#60/TotalPages>
80746         &#60Item>
80747             &#60ASIN>0446355453&#60/ASIN>
80748             &#60DetailPageURL>
80749                 http://www.amazon.com/
80750             &#60/DetailPageURL>
80751             &#60ItemAttributes>
80752                 &#60Author>Sidney Sheldon&#60/Author>
80753                 &#60Manufacturer>Warner Books&#60/Manufacturer>
80754                 &#60ProductGroup>Book&#60/ProductGroup>
80755                 &#60Title>Master of the Game&#60/Title>
80756             &#60/ItemAttributes>
80757         &#60/Item>
80758     &#60/Items>
80759 &#60/ItemSearchResponse>
80760  * </code></pre>
80761  * An object literal of this form could also be used as the {@link #data} config option.</p>
80762  * <p><b>Note:</b> This class accepts all of the configuration options of
80763  * <b>{@link Ext.data.reader.Xml XmlReader}</b>.</p>
80764  * @xtype xmlstore
80765  */
80766 Ext.define('Ext.data.XmlStore', {
80767     extend: 'Ext.data.Store',
80768     alternateClassName: 'Ext.data.XmlStore',
80769     alias: 'store.xml',
80770
80771     /**
80772      * @cfg {Ext.data.DataReader} reader @hide
80773      */
80774     constructor: function(config){
80775         config = config || {};
80776         config = config || {};
80777
80778         Ext.applyIf(config, {
80779             proxy: {
80780                 type: 'ajax',
80781                 reader: 'xml',
80782                 writer: 'xml'
80783             }
80784         });
80785
80786         this.callParent([config]);
80787     }
80788 });
80789
80790 /**
80791  * @author Ed Spencer
80792  *
80793  * Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and
80794  * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.
80795  * @private
80796  */
80797 Ext.define('Ext.data.proxy.Client', {
80798     extend: 'Ext.data.proxy.Proxy',
80799     alternateClassName: 'Ext.data.ClientProxy',
80800
80801     /**
80802      * Abstract function that must be implemented by each ClientProxy subclass. This should purge all record data
80803      * from the client side storage, as well as removing any supporting data (such as lists of record IDs)
80804      */
80805     clear: function() {
80806         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.");
80807     }
80808 });
80809 /**
80810  * @author Ed Spencer
80811  *
80812  * The JsonP proxy is useful when you need to load data from a domain other than the one your application is running on. If
80813  * your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its data
80814  * from http://domainB.com because cross-domain ajax requests are prohibited by the browser.
80815  *
80816  * We can get around this using a JsonP proxy. JsonP proxy injects a `<script>` tag into the DOM whenever an AJAX request
80817  * would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag that would be
80818  * injected might look like this:
80819  *
80820  *     <script src="http://domainB.com/users?callback=someCallback"></script>
80821  *
80822  * When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
80823  * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we want
80824  * to be notified when the result comes in and that it should call our callback function with the data it sends back. So
80825  * long as the server formats the response to look like this, everything will work:
80826  *
80827  *     someCallback({
80828  *         users: [
80829  *             {
80830  *                 id: 1,
80831  *                 name: "Ed Spencer",
80832  *                 email: "ed@sencha.com"
80833  *             }
80834  *         ]
80835  *     });
80836  *
80837  * As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the JSON
80838  * object that the server returned.
80839  *
80840  * JsonP proxy takes care of all of this automatically. It formats the url you pass, adding the callback parameter
80841  * automatically. It even creates a temporary callback function, waits for it to be called and then puts the data into
80842  * the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}. Here's how
80843  * we might set that up:
80844  *
80845  *     Ext.define('User', {
80846  *         extend: 'Ext.data.Model',
80847  *         fields: ['id', 'name', 'email']
80848  *     });
80849  *
80850  *     var store = Ext.create('Ext.data.Store', {
80851  *         model: 'User',
80852  *         proxy: {
80853  *             type: 'jsonp',
80854  *             url : 'http://domainB.com/users'
80855  *         }
80856  *     });
80857  *
80858  *     store.load();
80859  *
80860  * 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
80861  * like this:
80862  *
80863  *     <script src="http://domainB.com/users?callback=callback1"></script>
80864  *
80865  * # Customization
80866  *
80867  * This script tag can be customized using the {@link #callbackKey} configuration. For example:
80868  *
80869  *     var store = Ext.create('Ext.data.Store', {
80870  *         model: 'User',
80871  *         proxy: {
80872  *             type: 'jsonp',
80873  *             url : 'http://domainB.com/users',
80874  *             callbackKey: 'theCallbackFunction'
80875  *         }
80876  *     });
80877  *
80878  *     store.load();
80879  *
80880  * Would inject a script tag like this:
80881  *
80882  *     <script src="http://domainB.com/users?theCallbackFunction=callback1"></script>
80883  *
80884  * # Implementing on the server side
80885  *
80886  * The remote server side needs to be configured to return data in this format. Here are suggestions for how you might
80887  * achieve this using Java, PHP and ASP.net:
80888  *
80889  * Java:
80890  *
80891  *     boolean jsonP = false;
80892  *     String cb = request.getParameter("callback");
80893  *     if (cb != null) {
80894  *         jsonP = true;
80895  *         response.setContentType("text/javascript");
80896  *     } else {
80897  *         response.setContentType("application/x-json");
80898  *     }
80899  *     Writer out = response.getWriter();
80900  *     if (jsonP) {
80901  *         out.write(cb + "(");
80902  *     }
80903  *     out.print(dataBlock.toJsonString());
80904  *     if (jsonP) {
80905  *         out.write(");");
80906  *     }
80907  *
80908  * PHP:
80909  *
80910  *     $callback = $_REQUEST['callback'];
80911  *
80912  *     // Create the output object.
80913  *     $output = array('a' => 'Apple', 'b' => 'Banana');
80914  *
80915  *     //start output
80916  *     if ($callback) {
80917  *         header('Content-Type: text/javascript');
80918  *         echo $callback . '(' . json_encode($output) . ');';
80919  *     } else {
80920  *         header('Content-Type: application/x-json');
80921  *         echo json_encode($output);
80922  *     }
80923  *
80924  * ASP.net:
80925  *
80926  *     String jsonString = "{success: true}";
80927  *     String cb = Request.Params.Get("callback");
80928  *     String responseString = "";
80929  *     if (!String.IsNullOrEmpty(cb)) {
80930  *         responseString = cb + "(" + jsonString + ")";
80931  *     } else {
80932  *         responseString = jsonString;
80933  *     }
80934  *     Response.Write(responseString);
80935  */
80936 Ext.define('Ext.data.proxy.JsonP', {
80937     extend: 'Ext.data.proxy.Server',
80938     alternateClassName: 'Ext.data.ScriptTagProxy',
80939     alias: ['proxy.jsonp', 'proxy.scripttag'],
80940     requires: ['Ext.data.JsonP'],
80941
80942     defaultWriterType: 'base',
80943
80944     /**
80945      * @cfg {String} callbackKey
80946      * See {@link Ext.data.JsonP#callbackKey}.
80947      */
80948     callbackKey : 'callback',
80949
80950     /**
80951      * @cfg {String} recordParam
80952      * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString'). Defaults to
80953      * 'records'
80954      */
80955     recordParam: 'records',
80956
80957     /**
80958      * @cfg {Boolean} autoAppendParams
80959      * True to automatically append the request's params to the generated url. Defaults to true
80960      */
80961     autoAppendParams: true,
80962
80963     constructor: function(){
80964         this.addEvents(
80965             /**
80966              * @event
80967              * Fires when the server returns an exception
80968              * @param {Ext.data.proxy.Proxy} this
80969              * @param {Ext.data.Request} request The request that was sent
80970              * @param {Ext.data.Operation} operation The operation that triggered the request
80971              */
80972             'exception'
80973         );
80974         this.callParent(arguments);
80975     },
80976
80977     /**
80978      * @private
80979      * Performs the read request to the remote domain. JsonP proxy does not actually create an Ajax request,
80980      * instead we write out a <script> tag based on the configuration of the internal Ext.data.Request object
80981      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
80982      * @param {Function} callback A callback function to execute when the Operation has been completed
80983      * @param {Object} scope The scope to execute the callback in
80984      */
80985     doRequest: function(operation, callback, scope) {
80986         //generate the unique IDs for this request
80987         var me      = this,
80988             writer  = me.getWriter(),
80989             request = me.buildRequest(operation),
80990             params = request.params;
80991
80992         if (operation.allowWrite()) {
80993             request = writer.write(request);
80994         }
80995
80996         // apply JsonP proxy-specific attributes to the Request
80997         Ext.apply(request, {
80998             callbackKey: me.callbackKey,
80999             timeout: me.timeout,
81000             scope: me,
81001             disableCaching: false, // handled by the proxy
81002             callback: me.createRequestCallback(request, operation, callback, scope)
81003         });
81004
81005         // prevent doubling up
81006         if (me.autoAppendParams) {
81007             request.params = {};
81008         }
81009
81010         request.jsonp = Ext.data.JsonP.request(request);
81011         // restore on the request
81012         request.params = params;
81013         operation.setStarted();
81014         me.lastRequest = request;
81015
81016         return request;
81017     },
81018
81019     /**
81020      * @private
81021      * Creates and returns the function that is called when the request has completed. The returned function
81022      * should accept a Response object, which contains the response to be read by the configured Reader.
81023      * The third argument is the callback that should be called after the request has been completed and the Reader has decoded
81024      * the response. This callback will typically be the callback passed by a store, e.g. in proxy.read(operation, theCallback, scope)
81025      * theCallback refers to the callback argument received by this function.
81026      * See {@link #doRequest} for details.
81027      * @param {Ext.data.Request} request The Request object
81028      * @param {Ext.data.Operation} operation The Operation being executed
81029      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
81030      * passed to doRequest
81031      * @param {Object} scope The scope in which to execute the callback function
81032      * @return {Function} The callback function
81033      */
81034     createRequestCallback: function(request, operation, callback, scope) {
81035         var me = this;
81036
81037         return function(success, response, errorType) {
81038             delete me.lastRequest;
81039             me.processResponse(success, operation, request, response, callback, scope);
81040         };
81041     },
81042
81043     // inherit docs
81044     setException: function(operation, response) {
81045         operation.setException(operation.request.jsonp.errorType);
81046     },
81047
81048
81049     /**
81050      * Generates a url based on a given Ext.data.Request object. Adds the params and callback function name to the url
81051      * @param {Ext.data.Request} request The request object
81052      * @return {String} The url
81053      */
81054     buildUrl: function(request) {
81055         var me      = this,
81056             url     = me.callParent(arguments),
81057             params  = Ext.apply({}, request.params),
81058             filters = params.filters,
81059             records,
81060             filter, i;
81061
81062         delete params.filters;
81063
81064         if (me.autoAppendParams) {
81065             url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
81066         }
81067
81068         if (filters && filters.length) {
81069             for (i = 0; i < filters.length; i++) {
81070                 filter = filters[i];
81071
81072                 if (filter.value) {
81073                     url = Ext.urlAppend(url, filter.property + "=" + filter.value);
81074                 }
81075             }
81076         }
81077
81078         //if there are any records present, append them to the url also
81079         records = request.records;
81080
81081         if (Ext.isArray(records) && records.length > 0) {
81082             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.recordParam, me.encodeRecords(records)));
81083         }
81084
81085         return url;
81086     },
81087
81088     //inherit docs
81089     destroy: function() {
81090         this.abort();
81091         this.callParent();
81092     },
81093
81094     /**
81095      * Aborts the current server request if one is currently running
81096      */
81097     abort: function() {
81098         var lastRequest = this.lastRequest;
81099         if (lastRequest) {
81100             Ext.data.JsonP.abort(lastRequest.jsonp);
81101         }
81102     },
81103
81104     /**
81105      * Encodes an array of records into a string suitable to be appended to the script src url. This is broken out into
81106      * its own function so that it can be easily overridden.
81107      * @param {Ext.data.Model[]} records The records array
81108      * @return {String} The encoded records string
81109      */
81110     encodeRecords: function(records) {
81111         var encoded = "",
81112             i = 0,
81113             len = records.length;
81114
81115         for (; i < len; i++) {
81116             encoded += Ext.Object.toQueryString(records[i].data);
81117         }
81118
81119         return encoded;
81120     }
81121 });
81122
81123 /**
81124  * @author Ed Spencer
81125  *
81126  * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} and {@link
81127  * Ext.data.proxy.SessionStorage SessionStorage} proxies. It uses the new HTML5 key/value client-side storage objects to
81128  * save {@link Ext.data.Model model instances} for offline use.
81129  * @private
81130  */
81131 Ext.define('Ext.data.proxy.WebStorage', {
81132     extend: 'Ext.data.proxy.Client',
81133     alternateClassName: 'Ext.data.WebStorageProxy',
81134
81135     /**
81136      * @cfg {String} id
81137      * The unique ID used as the key in which all record data are stored in the local storage object.
81138      */
81139     id: undefined,
81140
81141     /**
81142      * Creates the proxy, throws an error if local storage is not supported in the current browser.
81143      * @param {Object} config (optional) Config object.
81144      */
81145     constructor: function(config) {
81146         this.callParent(arguments);
81147
81148         /**
81149          * @property {Object} cache
81150          * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
81151          */
81152         this.cache = {};
81153
81154         if (this.getStorageObject() === undefined) {
81155             Ext.Error.raise("Local Storage is not supported in this browser, please use another type of data proxy");
81156         }
81157
81158         //if an id is not given, try to use the store's id instead
81159         this.id = this.id || (this.store ? this.store.storeId : undefined);
81160
81161         if (this.id === undefined) {
81162             Ext.Error.raise("No unique id was provided to the local storage proxy. See Ext.data.proxy.LocalStorage documentation for details");
81163         }
81164
81165         this.initialize();
81166     },
81167
81168     //inherit docs
81169     create: function(operation, callback, scope) {
81170         var records = operation.records,
81171             length  = records.length,
81172             ids     = this.getIds(),
81173             id, record, i;
81174
81175         operation.setStarted();
81176
81177         for (i = 0; i < length; i++) {
81178             record = records[i];
81179
81180             if (record.phantom) {
81181                 record.phantom = false;
81182                 id = this.getNextId();
81183             } else {
81184                 id = record.getId();
81185             }
81186
81187             this.setRecord(record, id);
81188             ids.push(id);
81189         }
81190
81191         this.setIds(ids);
81192
81193         operation.setCompleted();
81194         operation.setSuccessful();
81195
81196         if (typeof callback == 'function') {
81197             callback.call(scope || this, operation);
81198         }
81199     },
81200
81201     //inherit docs
81202     read: function(operation, callback, scope) {
81203         //TODO: respect sorters, filters, start and limit options on the Operation
81204
81205         var records = [],
81206             ids     = this.getIds(),
81207             length  = ids.length,
81208             i, recordData, record;
81209
81210         //read a single record
81211         if (operation.id) {
81212             record = this.getRecord(operation.id);
81213
81214             if (record) {
81215                 records.push(record);
81216                 operation.setSuccessful();
81217             }
81218         } else {
81219             for (i = 0; i < length; i++) {
81220                 records.push(this.getRecord(ids[i]));
81221             }
81222             operation.setSuccessful();
81223         }
81224
81225         operation.setCompleted();
81226
81227         operation.resultSet = Ext.create('Ext.data.ResultSet', {
81228             records: records,
81229             total  : records.length,
81230             loaded : true
81231         });
81232
81233         if (typeof callback == 'function') {
81234             callback.call(scope || this, operation);
81235         }
81236     },
81237
81238     //inherit docs
81239     update: function(operation, callback, scope) {
81240         var records = operation.records,
81241             length  = records.length,
81242             ids     = this.getIds(),
81243             record, id, i;
81244
81245         operation.setStarted();
81246
81247         for (i = 0; i < length; i++) {
81248             record = records[i];
81249             this.setRecord(record);
81250
81251             //we need to update the set of ids here because it's possible that a non-phantom record was added
81252             //to this proxy - in which case the record's id would never have been added via the normal 'create' call
81253             id = record.getId();
81254             if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
81255                 ids.push(id);
81256             }
81257         }
81258         this.setIds(ids);
81259
81260         operation.setCompleted();
81261         operation.setSuccessful();
81262
81263         if (typeof callback == 'function') {
81264             callback.call(scope || this, operation);
81265         }
81266     },
81267
81268     //inherit
81269     destroy: function(operation, callback, scope) {
81270         var records = operation.records,
81271             length  = records.length,
81272             ids     = this.getIds(),
81273
81274             //newIds is a copy of ids, from which we remove the destroyed records
81275             newIds  = [].concat(ids),
81276             i;
81277
81278         for (i = 0; i < length; i++) {
81279             Ext.Array.remove(newIds, records[i].getId());
81280             this.removeRecord(records[i], false);
81281         }
81282
81283         this.setIds(newIds);
81284
81285         operation.setCompleted();
81286         operation.setSuccessful();
81287
81288         if (typeof callback == 'function') {
81289             callback.call(scope || this, operation);
81290         }
81291     },
81292
81293     /**
81294      * @private
81295      * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data.
81296      * @param {String} id The record's unique ID
81297      * @return {Ext.data.Model} The model instance
81298      */
81299     getRecord: function(id) {
81300         if (this.cache[id] === undefined) {
81301             var rawData = Ext.decode(this.getStorageObject().getItem(this.getRecordKey(id))),
81302                 data    = {},
81303                 Model   = this.model,
81304                 fields  = Model.prototype.fields.items,
81305                 length  = fields.length,
81306                 i, field, name, record;
81307
81308             for (i = 0; i < length; i++) {
81309                 field = fields[i];
81310                 name  = field.name;
81311
81312                 if (typeof field.decode == 'function') {
81313                     data[name] = field.decode(rawData[name]);
81314                 } else {
81315                     data[name] = rawData[name];
81316                 }
81317             }
81318
81319             record = new Model(data, id);
81320             record.phantom = false;
81321
81322             this.cache[id] = record;
81323         }
81324
81325         return this.cache[id];
81326     },
81327
81328     /**
81329      * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data.
81330      * @param {Ext.data.Model} record The model instance
81331      * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
81332      */
81333     setRecord: function(record, id) {
81334         if (id) {
81335             record.setId(id);
81336         } else {
81337             id = record.getId();
81338         }
81339
81340         var me = this,
81341             rawData = record.data,
81342             data    = {},
81343             model   = me.model,
81344             fields  = model.prototype.fields.items,
81345             length  = fields.length,
81346             i = 0,
81347             field, name, obj, key;
81348
81349         for (; i < length; i++) {
81350             field = fields[i];
81351             name  = field.name;
81352
81353             if (typeof field.encode == 'function') {
81354                 data[name] = field.encode(rawData[name], record);
81355             } else {
81356                 data[name] = rawData[name];
81357             }
81358         }
81359
81360         obj = me.getStorageObject();
81361         key = me.getRecordKey(id);
81362
81363         //keep the cache up to date
81364         me.cache[id] = record;
81365
81366         //iPad bug requires that we remove the item before setting it
81367         obj.removeItem(key);
81368         obj.setItem(key, Ext.encode(data));
81369     },
81370
81371     /**
81372      * @private
81373      * Physically removes a given record from the local storage. Used internally by {@link #destroy}, which you should
81374      * use instead because it updates the list of currently-stored record ids
81375      * @param {String/Number/Ext.data.Model} id The id of the record to remove, or an Ext.data.Model instance
81376      */
81377     removeRecord: function(id, updateIds) {
81378         var me = this,
81379             ids;
81380
81381         if (id.isModel) {
81382             id = id.getId();
81383         }
81384
81385         if (updateIds !== false) {
81386             ids = me.getIds();
81387             Ext.Array.remove(ids, id);
81388             me.setIds(ids);
81389         }
81390
81391         me.getStorageObject().removeItem(me.getRecordKey(id));
81392     },
81393
81394     /**
81395      * @private
81396      * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
81397      * storing data in the local storage object and should prevent naming collisions.
81398      * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
81399      * @return {String} The unique key for this record
81400      */
81401     getRecordKey: function(id) {
81402         if (id.isModel) {
81403             id = id.getId();
81404         }
81405
81406         return Ext.String.format("{0}-{1}", this.id, id);
81407     },
81408
81409     /**
81410      * @private
81411      * Returns the unique key used to store the current record counter for this proxy. This is used internally when
81412      * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
81413      * @return {String} The counter key
81414      */
81415     getRecordCounterKey: function() {
81416         return Ext.String.format("{0}-counter", this.id);
81417     },
81418
81419     /**
81420      * @private
81421      * Returns the array of record IDs stored in this Proxy
81422      * @return {Number[]} The record IDs. Each is cast as a Number
81423      */
81424     getIds: function() {
81425         var ids    = (this.getStorageObject().getItem(this.id) || "").split(","),
81426             length = ids.length,
81427             i;
81428
81429         if (length == 1 && ids[0] === "") {
81430             ids = [];
81431         } else {
81432             for (i = 0; i < length; i++) {
81433                 ids[i] = parseInt(ids[i], 10);
81434             }
81435         }
81436
81437         return ids;
81438     },
81439
81440     /**
81441      * @private
81442      * Saves the array of ids representing the set of all records in the Proxy
81443      * @param {Number[]} ids The ids to set
81444      */
81445     setIds: function(ids) {
81446         var obj = this.getStorageObject(),
81447             str = ids.join(",");
81448
81449         obj.removeItem(this.id);
81450
81451         if (!Ext.isEmpty(str)) {
81452             obj.setItem(this.id, str);
81453         }
81454     },
81455
81456     /**
81457      * @private
81458      * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey).
81459      * Increments the counter.
81460      * @return {Number} The id
81461      */
81462     getNextId: function() {
81463         var obj  = this.getStorageObject(),
81464             key  = this.getRecordCounterKey(),
81465             last = obj.getItem(key),
81466             ids, id;
81467
81468         if (last === null) {
81469             ids = this.getIds();
81470             last = ids[ids.length - 1] || 0;
81471         }
81472
81473         id = parseInt(last, 10) + 1;
81474         obj.setItem(key, id);
81475
81476         return id;
81477     },
81478
81479     /**
81480      * @private
81481      * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
81482      * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
81483      */
81484     initialize: function() {
81485         var storageObject = this.getStorageObject();
81486         storageObject.setItem(this.id, storageObject.getItem(this.id) || "");
81487     },
81488
81489     /**
81490      * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
81491      * storage object.
81492      */
81493     clear: function() {
81494         var obj = this.getStorageObject(),
81495             ids = this.getIds(),
81496             len = ids.length,
81497             i;
81498
81499         //remove all the records
81500         for (i = 0; i < len; i++) {
81501             this.removeRecord(ids[i]);
81502         }
81503
81504         //remove the supporting objects
81505         obj.removeItem(this.getRecordCounterKey());
81506         obj.removeItem(this.id);
81507     },
81508
81509     /**
81510      * @private
81511      * Abstract function which should return the storage object that data will be saved to. This must be implemented
81512      * in each subclass.
81513      * @return {Object} The storage object
81514      */
81515     getStorageObject: function() {
81516         Ext.Error.raise("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");
81517     }
81518 });
81519 /**
81520  * @author Ed Spencer
81521  *
81522  * The LocalStorageProxy uses the new HTML5 localStorage API to save {@link Ext.data.Model Model} data locally on the
81523  * client browser. HTML5 localStorage is a key-value store (e.g. cannot save complex objects like JSON), so
81524  * LocalStorageProxy automatically serializes and deserializes data when saving and retrieving it.
81525  *
81526  * localStorage is extremely useful for saving user-specific information without needing to build server-side
81527  * infrastructure to support it. Let's imagine we're writing a Twitter search application and want to save the user's
81528  * searches locally so they can easily perform a saved search again later. We'd start by creating a Search model:
81529  *
81530  *     Ext.define('Search', {
81531  *         fields: ['id', 'query'],
81532  *         extend: 'Ext.data.Model',
81533  *         proxy: {
81534  *             type: 'localstorage',
81535  *             id  : 'twitter-Searches'
81536  *         }
81537  *     });
81538  *
81539  * Our Search model contains just two fields - id and query - plus a Proxy definition. The only configuration we need to
81540  * pass to the LocalStorage proxy is an {@link #id}. This is important as it separates the Model data in this Proxy from
81541  * all others. The localStorage API puts all data into a single shared namespace, so by setting an id we enable
81542  * LocalStorageProxy to manage the saved Search data.
81543  *
81544  * Saving our data into localStorage is easy and would usually be done with a {@link Ext.data.Store Store}:
81545  *
81546  *     //our Store automatically picks up the LocalStorageProxy defined on the Search model
81547  *     var store = Ext.create('Ext.data.Store', {
81548  *         model: "Search"
81549  *     });
81550  *
81551  *     //loads any existing Search data from localStorage
81552  *     store.load();
81553  *
81554  *     //now add some Searches
81555  *     store.add({query: 'Sencha Touch'});
81556  *     store.add({query: 'Ext JS'});
81557  *
81558  *     //finally, save our Search data to localStorage
81559  *     store.sync();
81560  *
81561  * The LocalStorageProxy automatically gives our new Searches an id when we call store.sync(). It encodes the Model data
81562  * and places it into localStorage. We can also save directly to localStorage, bypassing the Store altogether:
81563  *
81564  *     var search = Ext.create('Search', {query: 'Sencha Animator'});
81565  *
81566  *     //uses the configured LocalStorageProxy to save the new Search to localStorage
81567  *     search.save();
81568  *
81569  * # Limitations
81570  *
81571  * If this proxy is used in a browser where local storage is not supported, the constructor will throw an error. A local
81572  * storage proxy requires a unique ID which is used as a key in which all record data are stored in the local storage
81573  * object.
81574  *
81575  * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
81576  * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
81577  */
81578 Ext.define('Ext.data.proxy.LocalStorage', {
81579     extend: 'Ext.data.proxy.WebStorage',
81580     alias: 'proxy.localstorage',
81581     alternateClassName: 'Ext.data.LocalStorageProxy',
81582     
81583     //inherit docs
81584     getStorageObject: function() {
81585         return window.localStorage;
81586     }
81587 });
81588 /**
81589  * @author Ed Spencer
81590  *
81591  * In-memory proxy. This proxy simply uses a local variable for data storage/retrieval, so its contents are lost on
81592  * every page refresh.
81593  *
81594  * Usually this Proxy isn't used directly, serving instead as a helper to a {@link Ext.data.Store Store} where a reader
81595  * is required to load data. For example, say we have a Store for a User model and have some inline data we want to
81596  * load, but this data isn't in quite the right format: we can use a MemoryProxy with a JsonReader to read it into our
81597  * Store:
81598  *
81599  *     //this is the model we will be using in the store
81600  *     Ext.define('User', {
81601  *         extend: 'Ext.data.Model',
81602  *         fields: [
81603  *             {name: 'id',    type: 'int'},
81604  *             {name: 'name',  type: 'string'},
81605  *             {name: 'phone', type: 'string', mapping: 'phoneNumber'}
81606  *         ]
81607  *     });
81608  *
81609  *     //this data does not line up to our model fields - the phone field is called phoneNumber
81610  *     var data = {
81611  *         users: [
81612  *             {
81613  *                 id: 1,
81614  *                 name: 'Ed Spencer',
81615  *                 phoneNumber: '555 1234'
81616  *             },
81617  *             {
81618  *                 id: 2,
81619  *                 name: 'Abe Elias',
81620  *                 phoneNumber: '666 1234'
81621  *             }
81622  *         ]
81623  *     };
81624  *
81625  *     //note how we set the 'root' in the reader to match the data structure above
81626  *     var store = Ext.create('Ext.data.Store', {
81627  *         autoLoad: true,
81628  *         model: 'User',
81629  *         data : data,
81630  *         proxy: {
81631  *             type: 'memory',
81632  *             reader: {
81633  *                 type: 'json',
81634  *                 root: 'users'
81635  *             }
81636  *         }
81637  *     });
81638  */
81639 Ext.define('Ext.data.proxy.Memory', {
81640     extend: 'Ext.data.proxy.Client',
81641     alias: 'proxy.memory',
81642     alternateClassName: 'Ext.data.MemoryProxy',
81643
81644     /**
81645      * @cfg {Ext.data.Model[]} data
81646      * Optional array of Records to load into the Proxy
81647      */
81648
81649     constructor: function(config) {
81650         this.callParent([config]);
81651
81652         //ensures that the reader has been instantiated properly
81653         this.setReader(this.reader);
81654     },
81655
81656     /**
81657      * Reads data from the configured {@link #data} object. Uses the Proxy's {@link #reader}, if present.
81658      * @param {Ext.data.Operation} operation The read Operation
81659      * @param {Function} callback The callback to call when reading has completed
81660      * @param {Object} scope The scope to call the callback function in
81661      */
81662     read: function(operation, callback, scope) {
81663         var me     = this,
81664             reader = me.getReader(),
81665             result = reader.read(me.data);
81666
81667         Ext.apply(operation, {
81668             resultSet: result
81669         });
81670
81671         operation.setCompleted();
81672         operation.setSuccessful();
81673         Ext.callback(callback, scope || me, [operation]);
81674     },
81675
81676     clear: Ext.emptyFn
81677 });
81678
81679 /**
81680  * @author Ed Spencer
81681  *
81682  * The Rest proxy is a specialization of the {@link Ext.data.proxy.Ajax AjaxProxy} which simply maps the four actions
81683  * (create, read, update and destroy) to RESTful HTTP verbs. For example, let's set up a {@link Ext.data.Model Model}
81684  * with an inline Rest proxy
81685  *
81686  *     Ext.define('User', {
81687  *         extend: 'Ext.data.Model',
81688  *         fields: ['id', 'name', 'email'],
81689  *
81690  *         proxy: {
81691  *             type: 'rest',
81692  *             url : '/users'
81693  *         }
81694  *     });
81695  *
81696  * 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
81697  * request to '/users':
81698  *
81699  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
81700  *
81701  *     user.save(); //POST /users
81702  *
81703  * Let's expand this a little and provide a callback for the {@link Ext.data.Model#save} call to update the Model once
81704  * it has been created. We'll assume the creation went successfully and that the server gave this user an ID of 123:
81705  *
81706  *     user.save({
81707  *         success: function(user) {
81708  *             user.set('name', 'Khan Noonien Singh');
81709  *
81710  *             user.save(); //PUT /users/123
81711  *         }
81712  *     });
81713  *
81714  * Now that we're no longer creating a new Model instance, the request method is changed to an HTTP PUT, targeting the
81715  * relevant url for that user. Now let's delete this user, which will use the DELETE method:
81716  *
81717  *         user.destroy(); //DELETE /users/123
81718  *
81719  * Finally, when we perform a load of a Model or Store, Rest proxy will use the GET method:
81720  *
81721  *     //1. Load via Store
81722  *
81723  *     //the Store automatically picks up the Proxy from the User model
81724  *     var store = Ext.create('Ext.data.Store', {
81725  *         model: 'User'
81726  *     });
81727  *
81728  *     store.load(); //GET /users
81729  *
81730  *     //2. Load directly from the Model
81731  *
81732  *     //GET /users/123
81733  *     Ext.ModelManager.getModel('User').load(123, {
81734  *         success: function(user) {
81735  *             console.log(user.getId()); //outputs 123
81736  *         }
81737  *     });
81738  *
81739  * # Url generation
81740  *
81741  * The Rest proxy is able to automatically generate the urls above based on two configuration options - {@link #appendId} and
81742  * {@link #format}. If appendId is true (it is by default) then Rest proxy will automatically append the ID of the Model
81743  * instance in question to the configured url, resulting in the '/users/123' that we saw above.
81744  *
81745  * If the request is not for a specific Model instance (e.g. loading a Store), the url is not appended with an id.
81746  * The Rest proxy will automatically insert a '/' before the ID if one is not already present.
81747  *
81748  *     new Ext.data.proxy.Rest({
81749  *         url: '/users',
81750  *         appendId: true //default
81751  *     });
81752  *
81753  *     // Collection url: /users
81754  *     // Instance url  : /users/123
81755  *
81756  * The Rest proxy can also optionally append a format string to the end of any generated url:
81757  *
81758  *     new Ext.data.proxy.Rest({
81759  *         url: '/users',
81760  *         format: 'json'
81761  *     });
81762  *
81763  *     // Collection url: /users.json
81764  *     // Instance url  : /users/123.json
81765  *
81766  * If further customization is needed, simply implement the {@link #buildUrl} method and add your custom generated url
81767  * onto the {@link Ext.data.Request Request} object that is passed to buildUrl. See [Rest proxy's implementation][1] for
81768  * an example of how to achieve this.
81769  *
81770  * Note that Rest proxy inherits from {@link Ext.data.proxy.Ajax AjaxProxy}, which already injects all of the sorter,
81771  * filter, group and paging options into the generated url. See the {@link Ext.data.proxy.Ajax AjaxProxy docs} for more
81772  * details.
81773  *
81774  * [1]: source/RestProxy.html#method-Ext.data.proxy.Rest-buildUrl
81775  */
81776 Ext.define('Ext.data.proxy.Rest', {
81777     extend: 'Ext.data.proxy.Ajax',
81778     alternateClassName: 'Ext.data.RestProxy',
81779     alias : 'proxy.rest',
81780     
81781     /**
81782      * @cfg {Boolean} appendId
81783      * True to automatically append the ID of a Model instance when performing a request based on that single instance.
81784      * See Rest proxy intro docs for more details. Defaults to true.
81785      */
81786     appendId: true,
81787     
81788     /**
81789      * @cfg {String} format
81790      * Optional data format to send to the server when making any request (e.g. 'json'). See the Rest proxy intro docs
81791      * for full details. Defaults to undefined.
81792      */
81793     
81794     /**
81795      * @cfg {Boolean} batchActions
81796      * True to batch actions of a particular type when synchronizing the store. Defaults to false.
81797      */
81798     batchActions: false,
81799     
81800     /**
81801      * Specialized version of buildUrl that incorporates the {@link #appendId} and {@link #format} options into the
81802      * generated url. Override this to provide further customizations, but remember to call the superclass buildUrl so
81803      * that additional parameters like the cache buster string are appended.
81804      * @param {Object} request
81805      */
81806     buildUrl: function(request) {
81807         var me        = this,
81808             operation = request.operation,
81809             records   = operation.records || [],
81810             record    = records[0],
81811             format    = me.format,
81812             url       = me.getUrl(request),
81813             id        = record ? record.getId() : operation.id;
81814         
81815         if (me.appendId && id) {
81816             if (!url.match(/\/$/)) {
81817                 url += '/';
81818             }
81819             
81820             url += id;
81821         }
81822         
81823         if (format) {
81824             if (!url.match(/\.$/)) {
81825                 url += '.';
81826             }
81827             
81828             url += format;
81829         }
81830         
81831         request.url = url;
81832         
81833         return me.callParent(arguments);
81834     }
81835 }, function() {
81836     Ext.apply(this.prototype, {
81837         /**
81838          * @property {Object} actionMethods
81839          * Mapping of action name to HTTP request method. These default to RESTful conventions for the 'create', 'read',
81840          * 'update' and 'destroy' actions (which map to 'POST', 'GET', 'PUT' and 'DELETE' respectively). This object
81841          * should not be changed except globally via {@link Ext#override Ext.override} - the {@link #getMethod} function
81842          * can be overridden instead.
81843          */
81844         actionMethods: {
81845             create : 'POST',
81846             read   : 'GET',
81847             update : 'PUT',
81848             destroy: 'DELETE'
81849         }
81850     });
81851 });
81852
81853 /**
81854  * @author Ed Spencer
81855  *
81856  * Proxy which uses HTML5 session storage as its data storage/retrieval mechanism. If this proxy is used in a browser
81857  * where session storage is not supported, the constructor will throw an error. A session storage proxy requires a
81858  * unique ID which is used as a key in which all record data are stored in the session storage object.
81859  *
81860  * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
81861  * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
81862  *
81863  * Proxies are almost always used with a {@link Ext.data.Store store}:
81864  *
81865  *     new Ext.data.Store({
81866  *         proxy: {
81867  *             type: 'sessionstorage',
81868  *             id  : 'myProxyKey'
81869  *         }
81870  *     });
81871  *
81872  * Alternatively you can instantiate the Proxy directly:
81873  *
81874  *     new Ext.data.proxy.SessionStorage({
81875  *         id  : 'myOtherProxyKey'
81876  *     });
81877  *
81878  * Note that session storage is different to local storage (see {@link Ext.data.proxy.LocalStorage}) - if a browser
81879  * session is ended (e.g. by closing the browser) then all data in a SessionStorageProxy are lost. Browser restarts
81880  * don't affect the {@link Ext.data.proxy.LocalStorage} - the data are preserved.
81881  */
81882 Ext.define('Ext.data.proxy.SessionStorage', {
81883     extend: 'Ext.data.proxy.WebStorage',
81884     alias: 'proxy.sessionstorage',
81885     alternateClassName: 'Ext.data.SessionStorageProxy',
81886     
81887     //inherit docs
81888     getStorageObject: function() {
81889         return window.sessionStorage;
81890     }
81891 });
81892
81893 /**
81894  * @author Ed Spencer
81895  * @class Ext.data.reader.Array
81896  * @extends Ext.data.reader.Json
81897  * 
81898  * <p>Data reader class to create an Array of {@link Ext.data.Model} objects from an Array.
81899  * Each element of that Array represents a row of data fields. The
81900  * fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
81901  * of the field definition if it exists, or the field's ordinal position in the definition.</p>
81902  * 
81903  * <p><u>Example code:</u></p>
81904  * 
81905 <pre><code>
81906 Employee = Ext.define('Employee', {
81907     extend: 'Ext.data.Model',
81908     fields: [
81909         'id',
81910         {name: 'name', mapping: 1},         // "mapping" only needed if an "id" field is present which
81911         {name: 'occupation', mapping: 2}    // precludes using the ordinal position as the index.        
81912     ]
81913 });
81914
81915 var myReader = new Ext.data.reader.Array({
81916     model: 'Employee'
81917 }, Employee);
81918 </code></pre>
81919  * 
81920  * <p>This would consume an Array like this:</p>
81921  * 
81922 <pre><code>
81923 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
81924 </code></pre>
81925  * 
81926  * @constructor
81927  * Create a new ArrayReader
81928  * @param {Object} meta Metadata configuration options.
81929  */
81930 Ext.define('Ext.data.reader.Array', {
81931     extend: 'Ext.data.reader.Json',
81932     alternateClassName: 'Ext.data.ArrayReader',
81933     alias : 'reader.array',
81934
81935     /**
81936      * @private
81937      * Most of the work is done for us by JsonReader, but we need to overwrite the field accessors to just
81938      * reference the correct position in the array.
81939      */
81940     buildExtractors: function() {
81941         this.callParent(arguments);
81942         
81943         var fields = this.model.prototype.fields.items,
81944             i = 0,
81945             length = fields.length,
81946             extractorFunctions = [],
81947             map;
81948         
81949         for (; i < length; i++) {
81950             map = fields[i].mapping;
81951             extractorFunctions.push(function(index) {
81952                 return function(data) {
81953                     return data[index];
81954                 };
81955             }(map !== null ? map : i));
81956         }
81957         
81958         this.extractorFunctions = extractorFunctions;
81959     }
81960 });
81961
81962 /**
81963  * @author Ed Spencer
81964  * @class Ext.data.reader.Xml
81965  * @extends Ext.data.reader.Reader
81966  *
81967  * <p>The XML Reader is used by a Proxy to read a server response that is sent back in XML format. This usually
81968  * happens as a result of loading a Store - for example we might create something like this:</p>
81969  *
81970 <pre><code>
81971 Ext.define('User', {
81972     extend: 'Ext.data.Model',
81973     fields: ['id', 'name', 'email']
81974 });
81975
81976 var store = Ext.create('Ext.data.Store', {
81977     model: 'User',
81978     proxy: {
81979         type: 'ajax',
81980         url : 'users.xml',
81981         reader: {
81982             type: 'xml',
81983             record: 'user'
81984         }
81985     }
81986 });
81987 </code></pre>
81988  *
81989  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
81990  * not already familiar with them.</p>
81991  *
81992  * <p>We created the simplest type of XML Reader possible by simply telling our {@link Ext.data.Store Store}'s
81993  * {@link Ext.data.proxy.Proxy Proxy} that we want a XML Reader. The Store automatically passes the configured model to the
81994  * Store, so it is as if we passed this instead:
81995  *
81996 <pre><code>
81997 reader: {
81998     type : 'xml',
81999     model: 'User',
82000     record: 'user'
82001 }
82002 </code></pre>
82003  *
82004  * <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>
82005  *
82006 <pre><code>
82007 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
82008 &lt;user&gt;
82009     &lt;id&gt;1&lt;/id&gt;
82010     &lt;name&gt;Ed Spencer&lt;/name&gt;
82011     &lt;email&gt;ed@sencha.com&lt;/email&gt;
82012 &lt;/user&gt;
82013 &lt;user&gt;
82014     &lt;id&gt;2&lt;/id&gt;
82015     &lt;name&gt;Abe Elias&lt;/name&gt;
82016     &lt;email&gt;abe@sencha.com&lt;/email&gt;
82017 &lt;/user&gt;
82018 </code></pre>
82019  *
82020  * <p>The XML Reader uses the configured {@link #record} option to pull out the data for each record - in this case we
82021  * set record to 'user', so each &lt;user&gt; above will be converted into a User model.</p>
82022  *
82023  * <p><u>Reading other XML formats</u></p>
82024  *
82025  * <p>If you already have your XML format defined and it doesn't look quite like what we have above, you can usually
82026  * pass XmlReader a couple of configuration options to make it parse your format. For example, we can use the
82027  * {@link #root} configuration to parse data that comes back like this:</p>
82028  *
82029 <pre><code>
82030 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
82031 &lt;users&gt;
82032     &lt;user&gt;
82033         &lt;id&gt;1&lt;/id&gt;
82034         &lt;name&gt;Ed Spencer&lt;/name&gt;
82035         &lt;email&gt;ed@sencha.com&lt;/email&gt;
82036     &lt;/user&gt;
82037     &lt;user&gt;
82038         &lt;id&gt;2&lt;/id&gt;
82039         &lt;name&gt;Abe Elias&lt;/name&gt;
82040         &lt;email&gt;abe@sencha.com&lt;/email&gt;
82041     &lt;/user&gt;
82042 &lt;/users&gt;
82043 </code></pre>
82044  *
82045  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
82046  *
82047 <pre><code>
82048 reader: {
82049     type  : 'xml',
82050     root  : 'users',
82051     record: 'user'
82052 }
82053 </code></pre>
82054  *
82055  * <p>Note that XmlReader doesn't care whether your {@link #root} and {@link #record} elements are nested deep inside
82056  * a larger structure, so a response like this will still work:
82057  *
82058 <pre><code>
82059 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
82060 &lt;deeply&gt;
82061     &lt;nested&gt;
82062         &lt;xml&gt;
82063             &lt;users&gt;
82064                 &lt;user&gt;
82065                     &lt;id&gt;1&lt;/id&gt;
82066                     &lt;name&gt;Ed Spencer&lt;/name&gt;
82067                     &lt;email&gt;ed@sencha.com&lt;/email&gt;
82068                 &lt;/user&gt;
82069                 &lt;user&gt;
82070                     &lt;id&gt;2&lt;/id&gt;
82071                     &lt;name&gt;Abe Elias&lt;/name&gt;
82072                     &lt;email&gt;abe@sencha.com&lt;/email&gt;
82073                 &lt;/user&gt;
82074             &lt;/users&gt;
82075         &lt;/xml&gt;
82076     &lt;/nested&gt;
82077 &lt;/deeply&gt;
82078 </code></pre>
82079  *
82080  * <p><u>Response metadata</u></p>
82081  *
82082  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
82083  * and the {@link #successProperty success status of the response}. These are typically included in the XML response
82084  * like this:</p>
82085  *
82086 <pre><code>
82087 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
82088 &lt;total&gt;100&lt;/total&gt;
82089 &lt;success&gt;true&lt;/success&gt;
82090 &lt;users&gt;
82091     &lt;user&gt;
82092         &lt;id&gt;1&lt;/id&gt;
82093         &lt;name&gt;Ed Spencer&lt;/name&gt;
82094         &lt;email&gt;ed@sencha.com&lt;/email&gt;
82095     &lt;/user&gt;
82096     &lt;user&gt;
82097         &lt;id&gt;2&lt;/id&gt;
82098         &lt;name&gt;Abe Elias&lt;/name&gt;
82099         &lt;email&gt;abe@sencha.com&lt;/email&gt;
82100     &lt;/user&gt;
82101 &lt;/users&gt;
82102 </code></pre>
82103  *
82104  * <p>If these properties are present in the XML response they can be parsed out by the XmlReader and used by the
82105  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
82106  * options:</p>
82107  *
82108 <pre><code>
82109 reader: {
82110     type: 'xml',
82111     root: 'users',
82112     totalProperty  : 'total',
82113     successProperty: 'success'
82114 }
82115 </code></pre>
82116  *
82117  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
82118  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
82119  * returned.</p>
82120  *
82121  * <p><u>Response format</u></p>
82122  *
82123  * <p><b>Note:</b> in order for the browser to parse a returned XML document, the Content-Type header in the HTTP
82124  * response must be set to "text/xml" or "application/xml". This is very important - the XmlReader will not
82125  * work correctly otherwise.</p>
82126  */
82127 Ext.define('Ext.data.reader.Xml', {
82128     extend: 'Ext.data.reader.Reader',
82129     alternateClassName: 'Ext.data.XmlReader',
82130     alias : 'reader.xml',
82131
82132     /**
82133      * @cfg {String} record (required)
82134      * The DomQuery path to the repeated element which contains record information.
82135      */
82136
82137     /**
82138      * @private
82139      * Creates a function to return some particular key of data from a response. The totalProperty and
82140      * successProperty are treated as special cases for type casting, everything else is just a simple selector.
82141      * @param {String} key
82142      * @return {Function}
82143      */
82144     createAccessor: function(expr) {
82145         var me = this;
82146
82147         if (Ext.isEmpty(expr)) {
82148             return Ext.emptyFn;
82149         }
82150
82151         if (Ext.isFunction(expr)) {
82152             return expr;
82153         }
82154
82155         return function(root) {
82156             return me.getNodeValue(Ext.DomQuery.selectNode(expr, root));
82157         };
82158     },
82159
82160     getNodeValue: function(node) {
82161         if (node && node.firstChild) {
82162             return node.firstChild.nodeValue;
82163         }
82164         return undefined;
82165     },
82166
82167     //inherit docs
82168     getResponseData: function(response) {
82169         var xml = response.responseXML;
82170
82171         if (!xml) {
82172             Ext.Error.raise({
82173                 response: response,
82174                 msg: 'XML data not found in the response'
82175             });
82176         }
82177
82178         return xml;
82179     },
82180
82181     /**
82182      * Normalizes the data object
82183      * @param {Object} data The raw data object
82184      * @return {Object} Returns the documentElement property of the data object if present, or the same object if not
82185      */
82186     getData: function(data) {
82187         return data.documentElement || data;
82188     },
82189
82190     /**
82191      * @private
82192      * Given an XML object, returns the Element that represents the root as configured by the Reader's meta data
82193      * @param {Object} data The XML data object
82194      * @return {XMLElement} The root node element
82195      */
82196     getRoot: function(data) {
82197         var nodeName = data.nodeName,
82198             root     = this.root;
82199
82200         if (!root || (nodeName && nodeName == root)) {
82201             return data;
82202         } else if (Ext.DomQuery.isXml(data)) {
82203             // This fix ensures we have XML data
82204             // Related to TreeStore calling getRoot with the root node, which isn't XML
82205             // Probably should be resolved in TreeStore at some point
82206             return Ext.DomQuery.selectNode(root, data);
82207         }
82208     },
82209
82210     /**
82211      * @private
82212      * We're just preparing the data for the superclass by pulling out the record nodes we want
82213      * @param {XMLElement} root The XML root node
82214      * @return {Ext.data.Model[]} The records
82215      */
82216     extractData: function(root) {
82217         var recordName = this.record;
82218
82219         if (!recordName) {
82220             Ext.Error.raise('Record is a required parameter');
82221         }
82222
82223         if (recordName != root.nodeName) {
82224             root = Ext.DomQuery.select(recordName, root);
82225         } else {
82226             root = [root];
82227         }
82228         return this.callParent([root]);
82229     },
82230
82231     /**
82232      * @private
82233      * See Ext.data.reader.Reader's getAssociatedDataRoot docs
82234      * @param {Object} data The raw data object
82235      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
82236      * @return {XMLElement} The root
82237      */
82238     getAssociatedDataRoot: function(data, associationName) {
82239         return Ext.DomQuery.select(associationName, data)[0];
82240     },
82241
82242     /**
82243      * Parses an XML document and returns a ResultSet containing the model instances
82244      * @param {Object} doc Parsed XML document
82245      * @return {Ext.data.ResultSet} The parsed result set
82246      */
82247     readRecords: function(doc) {
82248         //it's possible that we get passed an array here by associations. Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
82249         if (Ext.isArray(doc)) {
82250             doc = doc[0];
82251         }
82252
82253         /**
82254          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
82255          * @property xmlData
82256          * @type Object
82257          */
82258         this.xmlData = doc;
82259         return this.callParent([doc]);
82260     }
82261 });
82262 /**
82263  * @author Ed Spencer
82264  * @class Ext.data.writer.Xml
82265  * @extends Ext.data.writer.Writer
82266
82267 This class is used to write {@link Ext.data.Model} data to the server in an XML format.
82268 The {@link #documentRoot} property is used to specify the root element in the XML document.
82269 The {@link #record} option is used to specify the element name for each record that will make
82270 up the XML document.
82271
82272  * @markdown
82273  */
82274 Ext.define('Ext.data.writer.Xml', {
82275     
82276     /* Begin Definitions */
82277     
82278     extend: 'Ext.data.writer.Writer',
82279     alternateClassName: 'Ext.data.XmlWriter',
82280     
82281     alias: 'writer.xml',
82282     
82283     /* End Definitions */
82284     
82285     /**
82286      * @cfg {String} documentRoot The name of the root element of the document. Defaults to <tt>'xmlData'</tt>.
82287      * If there is more than 1 record and the root is not specified, the default document root will still be used
82288      * to ensure a valid XML document is created.
82289      */
82290     documentRoot: 'xmlData',
82291     
82292     /**
82293      * @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty and a root is required
82294      * to form a valid XML document.
82295      */
82296     defaultDocumentRoot: 'xmlData',
82297
82298     /**
82299      * @cfg {String} header A header to use in the XML document (such as setting the encoding or version).
82300      * Defaults to <tt>''</tt>.
82301      */
82302     header: '',
82303
82304     /**
82305      * @cfg {String} record The name of the node to use for each record. Defaults to <tt>'record'</tt>.
82306      */
82307     record: 'record',
82308
82309     //inherit docs
82310     writeRecords: function(request, data) {
82311         var me = this,
82312             xml = [],
82313             i = 0,
82314             len = data.length,
82315             root = me.documentRoot,
82316             record = me.record,
82317             needsRoot = data.length !== 1,
82318             item,
82319             key;
82320             
82321         // may not exist
82322         xml.push(me.header || '');
82323         
82324         if (!root && needsRoot) {
82325             root = me.defaultDocumentRoot;
82326         }
82327         
82328         if (root) {
82329             xml.push('<', root, '>');
82330         }
82331             
82332         for (; i < len; ++i) {
82333             item = data[i];
82334             xml.push('<', record, '>');
82335             for (key in item) {
82336                 if (item.hasOwnProperty(key)) {
82337                     xml.push('<', key, '>', item[key], '</', key, '>');
82338                 }
82339             }
82340             xml.push('</', record, '>');
82341         }
82342         
82343         if (root) {
82344             xml.push('</', root, '>');
82345         }
82346             
82347         request.xmlData = xml.join('');
82348         return request;
82349     }
82350 });
82351
82352 /**
82353  * @class Ext.direct.Event
82354  * A base class for all Ext.direct events. An event is
82355  * created after some kind of interaction with the server.
82356  * The event class is essentially just a data structure
82357  * to hold a Direct response.
82358  */
82359 Ext.define('Ext.direct.Event', {
82360
82361     /* Begin Definitions */
82362
82363     alias: 'direct.event',
82364
82365     requires: ['Ext.direct.Manager'],
82366
82367     /* End Definitions */
82368
82369     status: true,
82370
82371     /**
82372      * Creates new Event.
82373      * @param {Object} config (optional) Config object.
82374      */
82375     constructor: function(config) {
82376         Ext.apply(this, config);
82377     },
82378
82379     /**
82380      * Return the raw data for this event.
82381      * @return {Object} The data from the event
82382      */
82383     getData: function(){
82384         return this.data;
82385     }
82386 });
82387
82388 /**
82389  * @class Ext.direct.RemotingEvent
82390  * @extends Ext.direct.Event
82391  * An event that is fired when data is received from a 
82392  * {@link Ext.direct.RemotingProvider}. Contains a method to the
82393  * related transaction for the direct request, see {@link #getTransaction}
82394  */
82395 Ext.define('Ext.direct.RemotingEvent', {
82396     
82397     /* Begin Definitions */
82398    
82399     extend: 'Ext.direct.Event',
82400     
82401     alias: 'direct.rpc',
82402     
82403     /* End Definitions */
82404     
82405     /**
82406      * Get the transaction associated with this event.
82407      * @return {Ext.direct.Transaction} The transaction
82408      */
82409     getTransaction: function(){
82410         return this.transaction || Ext.direct.Manager.getTransaction(this.tid);
82411     }
82412 });
82413
82414 /**
82415  * @class Ext.direct.ExceptionEvent
82416  * @extends Ext.direct.RemotingEvent
82417  * An event that is fired when an exception is received from a {@link Ext.direct.RemotingProvider}
82418  */
82419 Ext.define('Ext.direct.ExceptionEvent', {
82420     
82421     /* Begin Definitions */
82422    
82423     extend: 'Ext.direct.RemotingEvent',
82424     
82425     alias: 'direct.exception',
82426     
82427     /* End Definitions */
82428    
82429    status: false
82430 });
82431
82432 /**
82433  * @class Ext.direct.Provider
82434  * <p>Ext.direct.Provider is an abstract class meant to be extended.</p>
82435  *
82436  * <p>For example Ext JS implements the following subclasses:</p>
82437  * <pre><code>
82438 Provider
82439 |
82440 +---{@link Ext.direct.JsonProvider JsonProvider}
82441     |
82442     +---{@link Ext.direct.PollingProvider PollingProvider}
82443     |
82444     +---{@link Ext.direct.RemotingProvider RemotingProvider}
82445  * </code></pre>
82446  * @abstract
82447  */
82448 Ext.define('Ext.direct.Provider', {
82449
82450     /* Begin Definitions */
82451
82452    alias: 'direct.provider',
82453
82454     mixins: {
82455         observable: 'Ext.util.Observable'
82456     },
82457
82458     /* End Definitions */
82459
82460    /**
82461      * @cfg {String} id
82462      * The unique id of the provider (defaults to an {@link Ext#id auto-assigned id}).
82463      * You should assign an id if you need to be able to access the provider later and you do
82464      * not have an object reference available, for example:
82465      * <pre><code>
82466 Ext.direct.Manager.addProvider({
82467     type: 'polling',
82468     url:  'php/poll.php',
82469     id:   'poll-provider'
82470 });
82471 var p = {@link Ext.direct.Manager}.{@link Ext.direct.Manager#getProvider getProvider}('poll-provider');
82472 p.disconnect();
82473      * </code></pre>
82474      */
82475
82476     constructor : function(config){
82477         var me = this;
82478
82479         Ext.apply(me, config);
82480         me.addEvents(
82481             /**
82482              * @event connect
82483              * Fires when the Provider connects to the server-side
82484              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
82485              */
82486             'connect',
82487             /**
82488              * @event disconnect
82489              * Fires when the Provider disconnects from the server-side
82490              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
82491              */
82492             'disconnect',
82493             /**
82494              * @event data
82495              * Fires when the Provider receives data from the server-side
82496              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
82497              * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
82498              */
82499             'data',
82500             /**
82501              * @event exception
82502              * Fires when the Provider receives an exception from the server-side
82503              */
82504             'exception'
82505         );
82506         me.mixins.observable.constructor.call(me, config);
82507     },
82508
82509     /**
82510      * Returns whether or not the server-side is currently connected.
82511      * Abstract method for subclasses to implement.
82512      */
82513     isConnected: function(){
82514         return false;
82515     },
82516
82517     /**
82518      * Abstract methods for subclasses to implement.
82519      * @method
82520      */
82521     connect: Ext.emptyFn,
82522
82523     /**
82524      * Abstract methods for subclasses to implement.
82525      * @method
82526      */
82527     disconnect: Ext.emptyFn
82528 });
82529
82530 /**
82531  * @class Ext.direct.JsonProvider
82532  * @extends Ext.direct.Provider
82533
82534 A base provider for communicating using JSON. This is an abstract class
82535 and should not be instanced directly.
82536
82537  * @markdown
82538  * @abstract
82539  */
82540
82541 Ext.define('Ext.direct.JsonProvider', {
82542
82543     /* Begin Definitions */
82544
82545     extend: 'Ext.direct.Provider',
82546
82547     alias: 'direct.jsonprovider',
82548
82549     uses: ['Ext.direct.ExceptionEvent'],
82550
82551     /* End Definitions */
82552
82553    /**
82554     * Parse the JSON response
82555     * @private
82556     * @param {Object} response The XHR response object
82557     * @return {Object} The data in the response.
82558     */
82559    parseResponse: function(response){
82560         if (!Ext.isEmpty(response.responseText)) {
82561             if (Ext.isObject(response.responseText)) {
82562                 return response.responseText;
82563             }
82564             return Ext.decode(response.responseText);
82565         }
82566         return null;
82567     },
82568
82569     /**
82570      * Creates a set of events based on the XHR response
82571      * @private
82572      * @param {Object} response The XHR response
82573      * @return {Ext.direct.Event[]} An array of Ext.direct.Event
82574      */
82575     createEvents: function(response){
82576         var data = null,
82577             events = [],
82578             event,
82579             i = 0,
82580             len;
82581
82582         try{
82583             data = this.parseResponse(response);
82584         } catch(e) {
82585             event = Ext.create('Ext.direct.ExceptionEvent', {
82586                 data: e,
82587                 xhr: response,
82588                 code: Ext.direct.Manager.self.exceptions.PARSE,
82589                 message: 'Error parsing json response: \n\n ' + data
82590             });
82591             return [event];
82592         }
82593
82594         if (Ext.isArray(data)) {
82595             for (len = data.length; i < len; ++i) {
82596                 events.push(this.createEvent(data[i]));
82597             }
82598         } else {
82599             events.push(this.createEvent(data));
82600         }
82601         return events;
82602     },
82603
82604     /**
82605      * Create an event from a response object
82606      * @param {Object} response The XHR response object
82607      * @return {Ext.direct.Event} The event
82608      */
82609     createEvent: function(response){
82610         return Ext.create('direct.' + response.type, response);
82611     }
82612 });
82613 /**
82614  * @class Ext.direct.PollingProvider
82615  * @extends Ext.direct.JsonProvider
82616  *
82617  * <p>Provides for repetitive polling of the server at distinct {@link #interval intervals}.
82618  * The initial request for data originates from the client, and then is responded to by the
82619  * server.</p>
82620  * 
82621  * <p>All configurations for the PollingProvider should be generated by the server-side
82622  * API portion of the Ext.Direct stack.</p>
82623  *
82624  * <p>An instance of PollingProvider may be created directly via the new keyword or by simply
82625  * specifying <tt>type = 'polling'</tt>.  For example:</p>
82626  * <pre><code>
82627 var pollA = new Ext.direct.PollingProvider({
82628     type:'polling',
82629     url: 'php/pollA.php',
82630 });
82631 Ext.direct.Manager.addProvider(pollA);
82632 pollA.disconnect();
82633
82634 Ext.direct.Manager.addProvider(
82635     {
82636         type:'polling',
82637         url: 'php/pollB.php',
82638         id: 'pollB-provider'
82639     }
82640 );
82641 var pollB = Ext.direct.Manager.getProvider('pollB-provider');
82642  * </code></pre>
82643  */
82644 Ext.define('Ext.direct.PollingProvider', {
82645     
82646     /* Begin Definitions */
82647     
82648     extend: 'Ext.direct.JsonProvider',
82649     
82650     alias: 'direct.pollingprovider',
82651     
82652     uses: ['Ext.direct.ExceptionEvent'],
82653     
82654     requires: ['Ext.Ajax', 'Ext.util.DelayedTask'],
82655     
82656     /* End Definitions */
82657     
82658     /**
82659      * @cfg {Number} interval
82660      * How often to poll the server-side in milliseconds. Defaults to every 3 seconds.
82661      */
82662     interval: 3000,
82663
82664     /**
82665      * @cfg {Object} baseParams
82666      * An object containing properties which are to be sent as parameters on every polling request
82667      */
82668     
82669     /**
82670      * @cfg {String/Function} url
82671      * The url which the PollingProvider should contact with each request. This can also be
82672      * an imported Ext.Direct method which will accept the baseParams as its only argument.
82673      */
82674
82675     // private
82676     constructor : function(config){
82677         this.callParent(arguments);
82678         this.addEvents(
82679             /**
82680              * @event beforepoll
82681              * Fired immediately before a poll takes place, an event handler can return false
82682              * in order to cancel the poll.
82683              * @param {Ext.direct.PollingProvider} this
82684              */
82685             'beforepoll',            
82686             /**
82687              * @event poll
82688              * This event has not yet been implemented.
82689              * @param {Ext.direct.PollingProvider} this
82690              */
82691             'poll'
82692         );
82693     },
82694
82695     // inherited
82696     isConnected: function(){
82697         return !!this.pollTask;
82698     },
82699
82700     /**
82701      * Connect to the server-side and begin the polling process. To handle each
82702      * response subscribe to the data event.
82703      */
82704     connect: function(){
82705         var me = this, url = me.url;
82706         
82707         if (url && !me.pollTask) {
82708             me.pollTask = Ext.TaskManager.start({
82709                 run: function(){
82710                     if (me.fireEvent('beforepoll', me) !== false) {
82711                         if (Ext.isFunction(url)) {
82712                             url(me.baseParams);
82713                         } else {
82714                             Ext.Ajax.request({
82715                                 url: url,
82716                                 callback: me.onData,
82717                                 scope: me,
82718                                 params: me.baseParams
82719                             });
82720                         }
82721                     }
82722                 },
82723                 interval: me.interval,
82724                 scope: me
82725             });
82726             me.fireEvent('connect', me);
82727         } else if (!url) {
82728             Ext.Error.raise('Error initializing PollingProvider, no url configured.');
82729         }
82730     },
82731
82732     /**
82733      * Disconnect from the server-side and stop the polling process. The disconnect
82734      * event will be fired on a successful disconnect.
82735      */
82736     disconnect: function(){
82737         var me = this;
82738         
82739         if (me.pollTask) {
82740             Ext.TaskManager.stop(me.pollTask);
82741             delete me.pollTask;
82742             me.fireEvent('disconnect', me);
82743         }
82744     },
82745
82746     // private
82747     onData: function(opt, success, response){
82748         var me = this, 
82749             i = 0, 
82750             len,
82751             events;
82752         
82753         if (success) {
82754             events = me.createEvents(response);
82755             for (len = events.length; i < len; ++i) {
82756                 me.fireEvent('data', me, events[i]);
82757             }
82758         } else {
82759             me.fireEvent('data', me, Ext.create('Ext.direct.ExceptionEvent', {
82760                 data: null,
82761                 code: Ext.direct.Manager.self.exceptions.TRANSPORT,
82762                 message: 'Unable to connect to the server.',
82763                 xhr: response
82764             }));
82765         }
82766     }
82767 });
82768 /**
82769  * Small utility class used internally to represent a Direct method.
82770  * @class Ext.direct.RemotingMethod
82771  * @ignore
82772  */
82773 Ext.define('Ext.direct.RemotingMethod', {
82774
82775     constructor: function(config){
82776         var me = this,
82777             params = Ext.isDefined(config.params) ? config.params : config.len,
82778             name;
82779
82780         me.name = config.name;
82781         me.formHandler = config.formHandler;
82782         if (Ext.isNumber(params)) {
82783             // given only the number of parameters
82784             me.len = params;
82785             me.ordered = true;
82786         } else {
82787             /*
82788              * Given an array of either
82789              * a) String
82790              * b) Objects with a name property. We may want to encode extra info in here later
82791              */
82792             me.params = [];
82793             Ext.each(params, function(param){
82794                 name = Ext.isObject(param) ? param.name : param;
82795                 me.params.push(name);
82796             });
82797         }
82798     },
82799
82800     /**
82801      * Takes the arguments for the Direct function and splits the arguments
82802      * from the scope and the callback.
82803      * @param {Array} args The arguments passed to the direct call
82804      * @return {Object} An object with 3 properties, args, callback & scope.
82805      */
82806     getCallData: function(args){
82807         var me = this,
82808             data = null,
82809             len  = me.len,
82810             params = me.params,
82811             callback,
82812             scope,
82813             name;
82814
82815         if (me.ordered) {
82816             callback = args[len];
82817             scope = args[len + 1];
82818             if (len !== 0) {
82819                 data = args.slice(0, len);
82820             }
82821         } else {
82822             data = Ext.apply({}, args[0]);
82823             callback = args[1];
82824             scope = args[2];
82825
82826             // filter out any non-existent properties
82827             for (name in data) {
82828                 if (data.hasOwnProperty(name)) {
82829                     if (!Ext.Array.contains(params, name)) {
82830                         delete data[name];
82831                     }
82832                 }
82833             }
82834         }
82835
82836         return {
82837             data: data,
82838             callback: callback,
82839             scope: scope
82840         };
82841     }
82842 });
82843
82844 /**
82845  * Supporting Class for Ext.Direct (not intended to be used directly).
82846  */
82847 Ext.define('Ext.direct.Transaction', {
82848     
82849     /* Begin Definitions */
82850    
82851     alias: 'direct.transaction',
82852     alternateClassName: 'Ext.Direct.Transaction',
82853    
82854     statics: {
82855         TRANSACTION_ID: 0
82856     },
82857    
82858     /* End Definitions */
82859
82860     /**
82861      * Creates new Transaction.
82862      * @param {Object} [config] Config object.
82863      */
82864     constructor: function(config){
82865         var me = this;
82866         
82867         Ext.apply(me, config);
82868         me.id = ++me.self.TRANSACTION_ID;
82869         me.retryCount = 0;
82870     },
82871    
82872     send: function(){
82873          this.provider.queueTransaction(this);
82874     },
82875
82876     retry: function(){
82877         this.retryCount++;
82878         this.send();
82879     },
82880
82881     getProvider: function(){
82882         return this.provider;
82883     }
82884 });
82885
82886 /**
82887  * @class Ext.direct.RemotingProvider
82888  * @extends Ext.direct.JsonProvider
82889  * 
82890  * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
82891  * server side methods on the client (a remote procedure call (RPC) type of
82892  * connection where the client can initiate a procedure on the server).</p>
82893  * 
82894  * <p>This allows for code to be organized in a fashion that is maintainable,
82895  * while providing a clear path between client and server, something that is
82896  * not always apparent when using URLs.</p>
82897  * 
82898  * <p>To accomplish this the server-side needs to describe what classes and methods
82899  * are available on the client-side. This configuration will typically be
82900  * outputted by the server-side Ext.Direct stack when the API description is built.</p>
82901  */
82902 Ext.define('Ext.direct.RemotingProvider', {
82903     
82904     /* Begin Definitions */
82905    
82906     alias: 'direct.remotingprovider',
82907     
82908     extend: 'Ext.direct.JsonProvider', 
82909     
82910     requires: [
82911         'Ext.util.MixedCollection', 
82912         'Ext.util.DelayedTask', 
82913         'Ext.direct.Transaction',
82914         'Ext.direct.RemotingMethod'
82915     ],
82916    
82917     /* End Definitions */
82918    
82919    /**
82920      * @cfg {Object} actions
82921      * Object literal defining the server side actions and methods. For example, if
82922      * the Provider is configured with:
82923      * <pre><code>
82924 "actions":{ // each property within the 'actions' object represents a server side Class 
82925     "TestAction":[ // array of methods within each server side Class to be   
82926     {              // stubbed out on client
82927         "name":"doEcho", 
82928         "len":1            
82929     },{
82930         "name":"multiply",// name of method
82931         "len":2           // The number of parameters that will be used to create an
82932                           // array of data to send to the server side function.
82933                           // Ensure the server sends back a Number, not a String. 
82934     },{
82935         "name":"doForm",
82936         "formHandler":true, // direct the client to use specialized form handling method 
82937         "len":1
82938     }]
82939 }
82940      * </code></pre>
82941      * <p>Note that a Store is not required, a server method can be called at any time.
82942      * In the following example a <b>client side</b> handler is used to call the
82943      * server side method "multiply" in the server-side "TestAction" Class:</p>
82944      * <pre><code>
82945 TestAction.multiply(
82946     2, 4, // pass two arguments to server, so specify len=2
82947     // callback function after the server is called
82948     // result: the result returned by the server
82949     //      e: Ext.direct.RemotingEvent object
82950     function(result, e){
82951         var t = e.getTransaction();
82952         var action = t.action; // server side Class called
82953         var method = t.method; // server side method called
82954         if(e.status){
82955             var answer = Ext.encode(result); // 8
82956     
82957         }else{
82958             var msg = e.message; // failure message
82959         }
82960     }
82961 );
82962      * </code></pre>
82963      * In the example above, the server side "multiply" function will be passed two
82964      * arguments (2 and 4).  The "multiply" method should return the value 8 which will be
82965      * available as the <tt>result</tt> in the example above. 
82966      */
82967     
82968     /**
82969      * @cfg {String/Object} namespace
82970      * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
82971      * Explicitly specify the namespace Object, or specify a String to have a
82972      * {@link Ext#namespace namespace created} implicitly.
82973      */
82974     
82975     /**
82976      * @cfg {String} url
82977      * <b>Required</b>. The url to connect to the {@link Ext.direct.Manager} server-side router. 
82978      */
82979     
82980     /**
82981      * @cfg {String} enableUrlEncode
82982      * Specify which param will hold the arguments for the method.
82983      * Defaults to <tt>'data'</tt>.
82984      */
82985     
82986     /**
82987      * @cfg {Number/Boolean} enableBuffer
82988      * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method
82989      * calls. If a number is specified this is the amount of time in milliseconds
82990      * to wait before sending a batched request.</p>
82991      * <br><p>Calls which are received within the specified timeframe will be
82992      * concatenated together and sent in a single request, optimizing the
82993      * application by reducing the amount of round trips that have to be made
82994      * to the server.</p>
82995      */
82996     enableBuffer: 10,
82997     
82998     /**
82999      * @cfg {Number} maxRetries
83000      * Number of times to re-attempt delivery on failure of a call.
83001      */
83002     maxRetries: 1,
83003     
83004     /**
83005      * @cfg {Number} timeout
83006      * The timeout to use for each request.
83007      */
83008     timeout: undefined,
83009     
83010     constructor : function(config){
83011         var me = this;
83012         me.callParent(arguments);
83013         me.addEvents(
83014             /**
83015              * @event beforecall
83016              * Fires immediately before the client-side sends off the RPC call.
83017              * By returning false from an event handler you can prevent the call from
83018              * executing.
83019              * @param {Ext.direct.RemotingProvider} provider
83020              * @param {Ext.direct.Transaction} transaction
83021              * @param {Object} meta The meta data
83022              */            
83023             'beforecall',            
83024             /**
83025              * @event call
83026              * Fires immediately after the request to the server-side is sent. This does
83027              * NOT fire after the response has come back from the call.
83028              * @param {Ext.direct.RemotingProvider} provider
83029              * @param {Ext.direct.Transaction} transaction
83030              * @param {Object} meta The meta data
83031              */            
83032             'call'
83033         );
83034         me.namespace = (Ext.isString(me.namespace)) ? Ext.ns(me.namespace) : me.namespace || window;
83035         me.transactions = Ext.create('Ext.util.MixedCollection');
83036         me.callBuffer = [];
83037     },
83038     
83039     /**
83040      * Initialize the API
83041      * @private
83042      */
83043     initAPI : function(){
83044         var actions = this.actions,
83045             namespace = this.namespace,
83046             action,
83047             cls,
83048             methods,
83049             i,
83050             len,
83051             method;
83052             
83053         for (action in actions) {
83054             cls = namespace[action];
83055             if (!cls) {
83056                 cls = namespace[action] = {};
83057             }
83058             methods = actions[action];
83059             
83060             for (i = 0, len = methods.length; i < len; ++i) {
83061                 method = Ext.create('Ext.direct.RemotingMethod', methods[i]);
83062                 cls[method.name] = this.createHandler(action, method);
83063             }
83064         }
83065     },
83066     
83067     /**
83068      * Create a handler function for a direct call.
83069      * @private
83070      * @param {String} action The action the call is for
83071      * @param {Object} method The details of the method
83072      * @return {Function} A JS function that will kick off the call
83073      */
83074     createHandler : function(action, method){
83075         var me = this,
83076             handler;
83077         
83078         if (!method.formHandler) {
83079             handler = function(){
83080                 me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
83081             };
83082         } else {
83083             handler = function(form, callback, scope){
83084                 me.configureFormRequest(action, method, form, callback, scope);
83085             };
83086         }
83087         handler.directCfg = {
83088             action: action,
83089             method: method
83090         };
83091         return handler;
83092     },
83093     
83094     // inherit docs
83095     isConnected: function(){
83096         return !!this.connected;
83097     },
83098
83099     // inherit docs
83100     connect: function(){
83101         var me = this;
83102         
83103         if (me.url) {
83104             me.initAPI();
83105             me.connected = true;
83106             me.fireEvent('connect', me);
83107         } else if(!me.url) {
83108             Ext.Error.raise('Error initializing RemotingProvider, no url configured.');
83109         }
83110     },
83111
83112     // inherit docs
83113     disconnect: function(){
83114         var me = this;
83115         
83116         if (me.connected) {
83117             me.connected = false;
83118             me.fireEvent('disconnect', me);
83119         }
83120     },
83121     
83122     /**
83123      * Run any callbacks related to the transaction.
83124      * @private
83125      * @param {Ext.direct.Transaction} transaction The transaction
83126      * @param {Ext.direct.Event} event The event
83127      */
83128     runCallback: function(transaction, event){
83129         var funcName = event.status ? 'success' : 'failure',
83130             callback,
83131             result;
83132         
83133         if (transaction && transaction.callback) {
83134             callback = transaction.callback;
83135             result = Ext.isDefined(event.result) ? event.result : event.data;
83136         
83137             if (Ext.isFunction(callback)) {
83138                 callback(result, event);
83139             } else {
83140                 Ext.callback(callback[funcName], callback.scope, [result, event]);
83141                 Ext.callback(callback.callback, callback.scope, [result, event]);
83142             }
83143         }
83144     },
83145     
83146     /**
83147      * React to the ajax request being completed
83148      * @private
83149      */
83150     onData: function(options, success, response){
83151         var me = this,
83152             i = 0,
83153             len,
83154             events,
83155             event,
83156             transaction,
83157             transactions;
83158             
83159         if (success) {
83160             events = me.createEvents(response);
83161             for (len = events.length; i < len; ++i) {
83162                 event = events[i];
83163                 transaction = me.getTransaction(event);
83164                 me.fireEvent('data', me, event);
83165                 if (transaction) {
83166                     me.runCallback(transaction, event, true);
83167                     Ext.direct.Manager.removeTransaction(transaction);
83168                 }
83169             }
83170         } else {
83171             transactions = [].concat(options.transaction);
83172             for (len = transactions.length; i < len; ++i) {
83173                 transaction = me.getTransaction(transactions[i]);
83174                 if (transaction && transaction.retryCount < me.maxRetries) {
83175                     transaction.retry();
83176                 } else {
83177                     event = Ext.create('Ext.direct.ExceptionEvent', {
83178                         data: null,
83179                         transaction: transaction,
83180                         code: Ext.direct.Manager.self.exceptions.TRANSPORT,
83181                         message: 'Unable to connect to the server.',
83182                         xhr: response
83183                     });
83184                     me.fireEvent('data', me, event);
83185                     if (transaction) {
83186                         me.runCallback(transaction, event, false);
83187                         Ext.direct.Manager.removeTransaction(transaction);
83188                     }
83189                 }
83190             }
83191         }
83192     },
83193     
83194     /**
83195      * Get transaction from XHR options
83196      * @private
83197      * @param {Object} options The options sent to the Ajax request
83198      * @return {Ext.direct.Transaction} The transaction, null if not found
83199      */
83200     getTransaction: function(options){
83201         return options && options.tid ? Ext.direct.Manager.getTransaction(options.tid) : null;
83202     },
83203     
83204     /**
83205      * Configure a direct request
83206      * @private
83207      * @param {String} action The action being executed
83208      * @param {Object} method The being executed
83209      */
83210     configureRequest: function(action, method, args){
83211         var me = this,
83212             callData = method.getCallData(args),
83213             data = callData.data, 
83214             callback = callData.callback, 
83215             scope = callData.scope,
83216             transaction;
83217
83218         transaction = Ext.create('Ext.direct.Transaction', {
83219             provider: me,
83220             args: args,
83221             action: action,
83222             method: method.name,
83223             data: data,
83224             callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
83225         });
83226
83227         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
83228             Ext.direct.Manager.addTransaction(transaction);
83229             me.queueTransaction(transaction);
83230             me.fireEvent('call', me, transaction, method);
83231         }
83232     },
83233     
83234     /**
83235      * Gets the Ajax call info for a transaction
83236      * @private
83237      * @param {Ext.direct.Transaction} transaction The transaction
83238      * @return {Object} The call params
83239      */
83240     getCallData: function(transaction){
83241         return {
83242             action: transaction.action,
83243             method: transaction.method,
83244             data: transaction.data,
83245             type: 'rpc',
83246             tid: transaction.id
83247         };
83248     },
83249     
83250     /**
83251      * Sends a request to the server
83252      * @private
83253      * @param {Object/Array} data The data to send
83254      */
83255     sendRequest : function(data){
83256         var me = this,
83257             request = {
83258                 url: me.url,
83259                 callback: me.onData,
83260                 scope: me,
83261                 transaction: data,
83262                 timeout: me.timeout
83263             }, callData,
83264             enableUrlEncode = me.enableUrlEncode,
83265             i = 0,
83266             len,
83267             params;
83268             
83269
83270         if (Ext.isArray(data)) {
83271             callData = [];
83272             for (len = data.length; i < len; ++i) {
83273                 callData.push(me.getCallData(data[i]));
83274             }
83275         } else {
83276             callData = me.getCallData(data);
83277         }
83278
83279         if (enableUrlEncode) {
83280             params = {};
83281             params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
83282             request.params = params;
83283         } else {
83284             request.jsonData = callData;
83285         }
83286         Ext.Ajax.request(request);
83287     },
83288     
83289     /**
83290      * Add a new transaction to the queue
83291      * @private
83292      * @param {Ext.direct.Transaction} transaction The transaction
83293      */
83294     queueTransaction: function(transaction){
83295         var me = this,
83296             enableBuffer = me.enableBuffer;
83297         
83298         if (transaction.form) {
83299             me.sendFormRequest(transaction);
83300             return;
83301         }
83302         
83303         me.callBuffer.push(transaction);
83304         if (enableBuffer) {
83305             if (!me.callTask) {
83306                 me.callTask = Ext.create('Ext.util.DelayedTask', me.combineAndSend, me);
83307             }
83308             me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
83309         } else {
83310             me.combineAndSend();
83311         }
83312     },
83313     
83314     /**
83315      * Combine any buffered requests and send them off
83316      * @private
83317      */
83318     combineAndSend : function(){
83319         var buffer = this.callBuffer,
83320             len = buffer.length;
83321             
83322         if (len > 0) {
83323             this.sendRequest(len == 1 ? buffer[0] : buffer);
83324             this.callBuffer = [];
83325         }
83326     },
83327     
83328     /**
83329      * Configure a form submission request
83330      * @private
83331      * @param {String} action The action being executed
83332      * @param {Object} method The method being executed
83333      * @param {HTMLElement} form The form being submitted
83334      * @param {Function} callback (optional) A callback to run after the form submits
83335      * @param {Object} scope (optional) A scope to execute the callback in
83336      */
83337     configureFormRequest : function(action, method, form, callback, scope){
83338         var me = this,
83339             transaction = Ext.create('Ext.direct.Transaction', {
83340                 provider: me,
83341                 action: action,
83342                 method: method.name,
83343                 args: [form, callback, scope],
83344                 callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
83345                 isForm: true
83346             }),
83347             isUpload,
83348             params;
83349
83350         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
83351             Ext.direct.Manager.addTransaction(transaction);
83352             isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
83353             
83354             params = {
83355                 extTID: transaction.id,
83356                 extAction: action,
83357                 extMethod: method.name,
83358                 extType: 'rpc',
83359                 extUpload: String(isUpload)
83360             };
83361             
83362             // change made from typeof callback check to callback.params
83363             // to support addl param passing in DirectSubmit EAC 6/2
83364             Ext.apply(transaction, {
83365                 form: Ext.getDom(form),
83366                 isUpload: isUpload,
83367                 params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
83368             });
83369             me.fireEvent('call', me, transaction, method);
83370             me.sendFormRequest(transaction);
83371         }
83372     },
83373     
83374     /**
83375      * Sends a form request
83376      * @private
83377      * @param {Ext.direct.Transaction} transaction The transaction to send
83378      */
83379     sendFormRequest: function(transaction){
83380         Ext.Ajax.request({
83381             url: this.url,
83382             params: transaction.params,
83383             callback: this.onData,
83384             scope: this,
83385             form: transaction.form,
83386             isUpload: transaction.isUpload,
83387             transaction: transaction
83388         });
83389     }
83390     
83391 });
83392
83393 /*
83394  * @class Ext.draw.Matrix
83395  * @private
83396  */
83397 Ext.define('Ext.draw.Matrix', {
83398
83399     /* Begin Definitions */
83400
83401     requires: ['Ext.draw.Draw'],
83402
83403     /* End Definitions */
83404
83405     constructor: function(a, b, c, d, e, f) {
83406         if (a != null) {
83407             this.matrix = [[a, c, e], [b, d, f], [0, 0, 1]];
83408         }
83409         else {
83410             this.matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
83411         }
83412     },
83413
83414     add: 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 += me.matrix[x][z] * matrix[z][y];
83428                 }
83429                 out[x][y] = res;
83430             }
83431         }
83432         me.matrix = out;
83433     },
83434
83435     prepend: function(a, b, c, d, e, f) {
83436         var me = this,
83437             out = [[], [], []],
83438             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
83439             x,
83440             y,
83441             z,
83442             res;
83443
83444         for (x = 0; x < 3; x++) {
83445             for (y = 0; y < 3; y++) {
83446                 res = 0;
83447                 for (z = 0; z < 3; z++) {
83448                     res += matrix[x][z] * me.matrix[z][y];
83449                 }
83450                 out[x][y] = res;
83451             }
83452         }
83453         me.matrix = out;
83454     },
83455
83456     invert: function() {
83457         var matrix = this.matrix,
83458             a = matrix[0][0],
83459             b = matrix[1][0],
83460             c = matrix[0][1],
83461             d = matrix[1][1],
83462             e = matrix[0][2],
83463             f = matrix[1][2],
83464             x = a * d - b * c;
83465         return new Ext.draw.Matrix(d / x, -b / x, -c / x, a / x, (c * f - d * e) / x, (b * e - a * f) / x);
83466     },
83467
83468     clone: function() {
83469         var matrix = this.matrix,
83470             a = matrix[0][0],
83471             b = matrix[1][0],
83472             c = matrix[0][1],
83473             d = matrix[1][1],
83474             e = matrix[0][2],
83475             f = matrix[1][2];
83476         return new Ext.draw.Matrix(a, b, c, d, e, f);
83477     },
83478
83479     translate: function(x, y) {
83480         this.prepend(1, 0, 0, 1, x, y);
83481     },
83482
83483     scale: function(x, y, cx, cy) {
83484         var me = this;
83485         if (y == null) {
83486             y = x;
83487         }
83488         me.add(1, 0, 0, 1, cx, cy);
83489         me.add(x, 0, 0, y, 0, 0);
83490         me.add(1, 0, 0, 1, -cx, -cy);
83491     },
83492
83493     rotate: function(a, x, y) {
83494         a = Ext.draw.Draw.rad(a);
83495         var me = this,
83496             cos = +Math.cos(a).toFixed(9),
83497             sin = +Math.sin(a).toFixed(9);
83498         me.add(cos, sin, -sin, cos, x, y);
83499         me.add(1, 0, 0, 1, -x, -y);
83500     },
83501
83502     x: function(x, y) {
83503         var matrix = this.matrix;
83504         return x * matrix[0][0] + y * matrix[0][1] + matrix[0][2];
83505     },
83506
83507     y: function(x, y) {
83508         var matrix = this.matrix;
83509         return x * matrix[1][0] + y * matrix[1][1] + matrix[1][2];
83510     },
83511
83512     get: function(i, j) {
83513         return + this.matrix[i][j].toFixed(4);
83514     },
83515
83516     toString: function() {
83517         var me = this;
83518         return [me.get(0, 0), me.get(0, 1), me.get(1, 0), me.get(1, 1), 0, 0].join();
83519     },
83520
83521     toSvg: function() {
83522         var me = this;
83523         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() + ")";
83524     },
83525
83526     toFilter: function() {
83527         var me = this;
83528         return "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand',FilterType=bilinear,M11=" + me.get(0, 0) +
83529             ", M12=" + me.get(0, 1) + ", M21=" + me.get(1, 0) + ", M22=" + me.get(1, 1) +
83530             ", Dx=" + me.get(0, 2) + ", Dy=" + me.get(1, 2) + ")";
83531     },
83532
83533     offset: function() {
83534         var matrix = this.matrix;
83535         return [(matrix[0][2] || 0).toFixed(4), (matrix[1][2] || 0).toFixed(4)];
83536     },
83537
83538     // Split matrix into Translate Scale, Shear, and Rotate
83539     split: function () {
83540         function norm(a) {
83541             return a[0] * a[0] + a[1] * a[1];
83542         }
83543         function normalize(a) {
83544             var mag = Math.sqrt(norm(a));
83545             a[0] /= mag;
83546             a[1] /= mag;
83547         }
83548         var matrix = this.matrix,
83549             out = {
83550                 translateX: matrix[0][2],
83551                 translateY: matrix[1][2]
83552             },
83553             row;
83554
83555         // scale and shear
83556         row = [[matrix[0][0], matrix[0][1]], [matrix[1][0], matrix[1][1]]];
83557         out.scaleX = Math.sqrt(norm(row[0]));
83558         normalize(row[0]);
83559
83560         out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
83561         row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
83562
83563         out.scaleY = Math.sqrt(norm(row[1]));
83564         normalize(row[1]);
83565         out.shear /= out.scaleY;
83566
83567         // rotation
83568         out.rotate = Math.asin(-row[0][1]);
83569
83570         out.isSimple = !+out.shear.toFixed(9) && (out.scaleX.toFixed(9) == out.scaleY.toFixed(9) || !out.rotate);
83571
83572         return out;
83573     }
83574 });
83575
83576 // private - DD implementation for Panels
83577 Ext.define('Ext.draw.SpriteDD', {
83578     extend: 'Ext.dd.DragSource',
83579
83580     constructor : function(sprite, cfg){
83581         var me = this,
83582             el = sprite.el;
83583         me.sprite = sprite;
83584         me.el = el;
83585         me.dragData = {el: el, sprite: sprite};
83586         me.callParent([el, cfg]);
83587         me.sprite.setStyle('cursor', 'move');
83588     },
83589
83590     showFrame: Ext.emptyFn,
83591     createFrame : Ext.emptyFn,
83592
83593     getDragEl : function(e){
83594         return this.el;
83595     },
83596     
83597     getRegion: function() {
83598         var me = this,
83599             el = me.el,
83600             pos, x1, x2, y1, y2, t, r, b, l, bbox, sprite;
83601         
83602         sprite = me.sprite;
83603         bbox = sprite.getBBox();
83604         
83605         try {
83606             pos = Ext.Element.getXY(el);
83607         } catch (e) { }
83608
83609         if (!pos) {
83610             return null;
83611         }
83612
83613         x1 = pos[0];
83614         x2 = x1 + bbox.width;
83615         y1 = pos[1];
83616         y2 = y1 + bbox.height;
83617         
83618         return Ext.create('Ext.util.Region', y1, x2, y2, x1);
83619     },
83620
83621     /*
83622       TODO(nico): Cumulative translations in VML are handled
83623       differently than in SVG. While in SVG we specify the translation
83624       relative to the original x, y position attributes, in VML the translation
83625       is a delta between the last position of the object (modified by the last
83626       translation) and the new one.
83627       
83628       In VML the translation alters the position
83629       of the object, we should change that or alter the SVG impl.
83630     */
83631      
83632     startDrag: function(x, y) {
83633         var me = this,
83634             attr = me.sprite.attr;
83635         me.prev = me.sprite.surface.transformToViewBox(x, y);
83636     },
83637
83638     onDrag: function(e) {
83639         var xy = e.getXY(),
83640             me = this,
83641             sprite = me.sprite,
83642             attr = sprite.attr, dx, dy;
83643         xy = me.sprite.surface.transformToViewBox(xy[0], xy[1]);
83644         dx = xy[0] - me.prev[0];
83645         dy = xy[1] - me.prev[1];
83646         sprite.setAttributes({
83647             translate: {
83648                 x: attr.translation.x + dx,
83649                 y: attr.translation.y + dy
83650             }
83651         }, true);
83652         me.prev = xy;
83653     },
83654
83655     setDragElPos: function () {
83656         // Disable automatic DOM move in DD that spoils layout of VML engine.
83657         return false;
83658     }
83659 });
83660 /**
83661  * A Sprite is an object rendered in a Drawing surface.
83662  *
83663  * # Translation
83664  *
83665  * For translate, the configuration object contains x and y attributes that indicate where to
83666  * translate the object. For example:
83667  *
83668  *     sprite.setAttributes({
83669  *       translate: {
83670  *        x: 10,
83671  *        y: 10
83672  *       }
83673  *     }, true);
83674  *
83675  *
83676  * # Rotation
83677  *
83678  * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
83679  * and a `degrees` attribute that specifies the rotation in degrees. For example:
83680  *
83681  *     sprite.setAttributes({
83682  *       rotate: {
83683  *        degrees: 90
83684  *       }
83685  *     }, true);
83686  *
83687  * That example will create a 90 degrees rotation using the centroid of the Sprite as center of rotation, whereas:
83688  *
83689  *     sprite.setAttributes({
83690  *       rotate: {
83691  *        x: 0,
83692  *        y: 0,
83693  *        degrees: 90
83694  *       }
83695  *     }, true);
83696  *
83697  * will create a rotation around the `(0, 0)` axis.
83698  *
83699  *
83700  * # Scaling
83701  *
83702  * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
83703  *
83704  *     sprite.setAttributes({
83705  *       scale: {
83706  *        x: 10,
83707  *        y: 3
83708  *       }
83709  *     }, true);
83710  *
83711  * You can also specify the center of scaling by adding `cx` and `cy` as properties:
83712  *
83713  *     sprite.setAttributes({
83714  *       scale: {
83715  *        cx: 0,
83716  *        cy: 0,
83717  *        x: 10,
83718  *        y: 3
83719  *       }
83720  *     }, true);
83721  *
83722  * That last example will scale a sprite taking as centers of scaling the `(0, 0)` coordinate.
83723  *
83724  *
83725  * # Creating and adding a Sprite to a Surface
83726  *
83727  * Sprites can be created with a reference to a {@link Ext.draw.Surface}
83728  *
83729  *     var drawComponent = Ext.create('Ext.draw.Component', options here...);
83730  *
83731  *     var sprite = Ext.create('Ext.draw.Sprite', {
83732  *         type: 'circle',
83733  *         fill: '#ff0',
83734  *         surface: drawComponent.surface,
83735  *         radius: 5
83736  *     });
83737  *
83738  * Sprites can also be added to the surface as a configuration object:
83739  *
83740  *     var sprite = drawComponent.surface.add({
83741  *         type: 'circle',
83742  *         fill: '#ff0',
83743  *         radius: 5
83744  *     });
83745  *
83746  * In order to properly apply properties and render the sprite we have to
83747  * `show` the sprite setting the option `redraw` to `true`:
83748  *
83749  *     sprite.show(true);
83750  *
83751  * The constructor configuration object of the Sprite can also be used and passed into the {@link Ext.draw.Surface}
83752  * add method to append a new sprite to the canvas. For example:
83753  *
83754  *     drawComponent.surface.add({
83755  *         type: 'circle',
83756  *         fill: '#ffc',
83757  *         radius: 100,
83758  *         x: 100,
83759  *         y: 100
83760  *     });
83761  */
83762 Ext.define('Ext.draw.Sprite', {
83763
83764     /* Begin Definitions */
83765
83766     mixins: {
83767         observable: 'Ext.util.Observable',
83768         animate: 'Ext.util.Animate'
83769     },
83770
83771     requires: ['Ext.draw.SpriteDD'],
83772
83773     /* End Definitions */
83774
83775     /**
83776      * @cfg {String} type The type of the sprite. Possible options are 'circle', 'path', 'rect', 'text', 'square', 'image'
83777      */
83778
83779     /**
83780      * @cfg {Number} width Used in rectangle sprites, the width of the rectangle
83781      */
83782
83783     /**
83784      * @cfg {Number} height Used in rectangle sprites, the height of the rectangle
83785      */
83786
83787     /**
83788      * @cfg {Number} size Used in square sprites, the dimension of the square
83789      */
83790
83791     /**
83792      * @cfg {Number} radius Used in circle sprites, the radius of the circle
83793      */
83794
83795     /**
83796      * @cfg {Number} x The position along the x-axis
83797      */
83798
83799     /**
83800      * @cfg {Number} y The position along the y-axis
83801      */
83802
83803     /**
83804      * @cfg {Array} path Used in path sprites, the path of the sprite written in SVG-like path syntax
83805      */
83806
83807     /**
83808      * @cfg {Number} opacity The opacity of the sprite
83809      */
83810
83811     /**
83812      * @cfg {String} fill The fill color
83813      */
83814
83815     /**
83816      * @cfg {String} stroke The stroke color
83817      */
83818
83819     /**
83820      * @cfg {Number} stroke-width The width of the stroke
83821      */
83822
83823     /**
83824      * @cfg {String} font Used with text type sprites. The full font description. Uses the same syntax as the CSS font parameter
83825      */
83826
83827     /**
83828      * @cfg {String} text Used with text type sprites. The text itself
83829      */
83830
83831     /**
83832      * @cfg {String/String[]} group The group that this sprite belongs to, or an array of groups. Only relevant when added to a
83833      * {@link Ext.draw.Surface}
83834      */
83835
83836     /**
83837      * @cfg {Boolean} draggable True to make the sprite draggable.
83838      */
83839
83840     dirty: false,
83841     dirtyHidden: false,
83842     dirtyTransform: false,
83843     dirtyPath: true,
83844     dirtyFont: true,
83845     zIndexDirty: true,
83846     isSprite: true,
83847     zIndex: 0,
83848     fontProperties: [
83849         'font',
83850         'font-size',
83851         'font-weight',
83852         'font-style',
83853         'font-family',
83854         'text-anchor',
83855         'text'
83856     ],
83857     pathProperties: [
83858         'x',
83859         'y',
83860         'd',
83861         'path',
83862         'height',
83863         'width',
83864         'radius',
83865         'r',
83866         'rx',
83867         'ry',
83868         'cx',
83869         'cy'
83870     ],
83871     constructor: function(config) {
83872         var me = this;
83873         config = config || {};
83874         me.id = Ext.id(null, 'ext-sprite-');
83875         me.transformations = [];
83876         Ext.copyTo(this, config, 'surface,group,type,draggable');
83877         //attribute bucket
83878         me.bbox = {};
83879         me.attr = {
83880             zIndex: 0,
83881             translation: {
83882                 x: null,
83883                 y: null
83884             },
83885             rotation: {
83886                 degrees: null,
83887                 x: null,
83888                 y: null
83889             },
83890             scaling: {
83891                 x: null,
83892                 y: null,
83893                 cx: null,
83894                 cy: null
83895             }
83896         };
83897         //delete not bucket attributes
83898         delete config.surface;
83899         delete config.group;
83900         delete config.type;
83901         delete config.draggable;
83902         me.setAttributes(config);
83903         me.addEvents(
83904             'beforedestroy',
83905             'destroy',
83906             'render',
83907             'mousedown',
83908             'mouseup',
83909             'mouseover',
83910             'mouseout',
83911             'mousemove',
83912             'click'
83913         );
83914         me.mixins.observable.constructor.apply(this, arguments);
83915     },
83916
83917     /**
83918      * @property {Ext.dd.DragSource} dd
83919      * If this Sprite is configured {@link #draggable}, this property will contain
83920      * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.
83921      *
83922      * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
83923      * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
83924      */
83925
83926     initDraggable: function() {
83927         var me = this;
83928         me.draggable = true;
83929         //create element if it doesn't exist.
83930         if (!me.el) {
83931             me.surface.createSpriteElement(me);
83932         }
83933         me.dd = Ext.create('Ext.draw.SpriteDD', me, Ext.isBoolean(me.draggable) ? null : me.draggable);
83934         me.on('beforedestroy', me.dd.destroy, me.dd);
83935     },
83936
83937     /**
83938      * Change the attributes of the sprite.
83939      * @param {Object} attrs attributes to be changed on the sprite.
83940      * @param {Boolean} redraw Flag to immediatly draw the change.
83941      * @return {Ext.draw.Sprite} this
83942      */
83943     setAttributes: function(attrs, redraw) {
83944         var me = this,
83945             fontProps = me.fontProperties,
83946             fontPropsLength = fontProps.length,
83947             pathProps = me.pathProperties,
83948             pathPropsLength = pathProps.length,
83949             hasSurface = !!me.surface,
83950             custom = hasSurface && me.surface.customAttributes || {},
83951             spriteAttrs = me.attr,
83952             attr, i, translate, translation, rotate, rotation, scale, scaling;
83953
83954         attrs = Ext.apply({}, attrs);
83955         for (attr in custom) {
83956             if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
83957                 Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
83958             }
83959         }
83960
83961         // Flag a change in hidden
83962         if (!!attrs.hidden !== !!spriteAttrs.hidden) {
83963             me.dirtyHidden = true;
83964         }
83965
83966         // Flag path change
83967         for (i = 0; i < pathPropsLength; i++) {
83968             attr = pathProps[i];
83969             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
83970                 me.dirtyPath = true;
83971                 break;
83972             }
83973         }
83974
83975         // Flag zIndex change
83976         if ('zIndex' in attrs) {
83977             me.zIndexDirty = true;
83978         }
83979
83980         // Flag font/text change
83981         for (i = 0; i < fontPropsLength; i++) {
83982             attr = fontProps[i];
83983             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
83984                 me.dirtyFont = true;
83985                 break;
83986             }
83987         }
83988
83989         translate = attrs.translate;
83990         translation = spriteAttrs.translation;
83991         if (translate) {
83992             if ((translate.x && translate.x !== translation.x) ||
83993                 (translate.y && translate.y !== translation.y)) {
83994                 Ext.apply(translation, translate);
83995                 me.dirtyTransform = true;
83996             }
83997             delete attrs.translate;
83998         }
83999
84000         rotate = attrs.rotate;
84001         rotation = spriteAttrs.rotation;
84002         if (rotate) {
84003             if ((rotate.x && rotate.x !== rotation.x) ||
84004                 (rotate.y && rotate.y !== rotation.y) ||
84005                 (rotate.degrees && rotate.degrees !== rotation.degrees)) {
84006                 Ext.apply(rotation, rotate);
84007                 me.dirtyTransform = true;
84008             }
84009             delete attrs.rotate;
84010         }
84011
84012         scale = attrs.scale;
84013         scaling = spriteAttrs.scaling;
84014         if (scale) {
84015             if ((scale.x && scale.x !== scaling.x) ||
84016                 (scale.y && scale.y !== scaling.y) ||
84017                 (scale.cx && scale.cx !== scaling.cx) ||
84018                 (scale.cy && scale.cy !== scaling.cy)) {
84019                 Ext.apply(scaling, scale);
84020                 me.dirtyTransform = true;
84021             }
84022             delete attrs.scale;
84023         }
84024
84025         Ext.apply(spriteAttrs, attrs);
84026         me.dirty = true;
84027
84028         if (redraw === true && hasSurface) {
84029             me.redraw();
84030         }
84031         return this;
84032     },
84033
84034     /**
84035      * Retrieves the bounding box of the sprite.
84036      * This will be returned as an object with x, y, width, and height properties.
84037      * @return {Object} bbox
84038      */
84039     getBBox: function() {
84040         return this.surface.getBBox(this);
84041     },
84042
84043     setText: function(text) {
84044         return this.surface.setText(this, text);
84045     },
84046
84047     /**
84048      * Hides the sprite.
84049      * @param {Boolean} redraw Flag to immediatly draw the change.
84050      * @return {Ext.draw.Sprite} this
84051      */
84052     hide: function(redraw) {
84053         this.setAttributes({
84054             hidden: true
84055         }, redraw);
84056         return this;
84057     },
84058
84059     /**
84060      * Shows the sprite.
84061      * @param {Boolean} redraw Flag to immediatly draw the change.
84062      * @return {Ext.draw.Sprite} this
84063      */
84064     show: function(redraw) {
84065         this.setAttributes({
84066             hidden: false
84067         }, redraw);
84068         return this;
84069     },
84070
84071     /**
84072      * Removes the sprite.
84073      */
84074     remove: function() {
84075         if (this.surface) {
84076             this.surface.remove(this);
84077             return true;
84078         }
84079         return false;
84080     },
84081
84082     onRemove: function() {
84083         this.surface.onRemove(this);
84084     },
84085
84086     /**
84087      * Removes the sprite and clears all listeners.
84088      */
84089     destroy: function() {
84090         var me = this;
84091         if (me.fireEvent('beforedestroy', me) !== false) {
84092             me.remove();
84093             me.surface.onDestroy(me);
84094             me.clearListeners();
84095             me.fireEvent('destroy');
84096         }
84097     },
84098
84099     /**
84100      * Redraws the sprite.
84101      * @return {Ext.draw.Sprite} this
84102      */
84103     redraw: function() {
84104         this.surface.renderItem(this);
84105         return this;
84106     },
84107
84108     /**
84109      * Wrapper for setting style properties, also takes single object parameter of multiple styles.
84110      * @param {String/Object} property The style property to be set, or an object of multiple styles.
84111      * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
84112      * @return {Ext.draw.Sprite} this
84113      */
84114     setStyle: function() {
84115         this.el.setStyle.apply(this.el, arguments);
84116         return this;
84117     },
84118
84119     /**
84120      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.  Note this method
84121      * is severly limited in VML.
84122      * @param {String/String[]} className The CSS class to add, or an array of classes
84123      * @return {Ext.draw.Sprite} this
84124      */
84125     addCls: function(obj) {
84126         this.surface.addCls(this, obj);
84127         return this;
84128     },
84129
84130     /**
84131      * Removes one or more CSS classes from the element.
84132      * @param {String/String[]} className The CSS class to remove, or an array of classes.  Note this method
84133      * is severly limited in VML.
84134      * @return {Ext.draw.Sprite} this
84135      */
84136     removeCls: function(obj) {
84137         this.surface.removeCls(this, obj);
84138         return this;
84139     }
84140 });
84141
84142 /**
84143  * @class Ext.draw.engine.Svg
84144  * @extends Ext.draw.Surface
84145  * Provides specific methods to draw with SVG.
84146  */
84147 Ext.define('Ext.draw.engine.Svg', {
84148
84149     /* Begin Definitions */
84150
84151     extend: 'Ext.draw.Surface',
84152
84153     requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
84154
84155     /* End Definitions */
84156
84157     engine: 'Svg',
84158
84159     trimRe: /^\s+|\s+$/g,
84160     spacesRe: /\s+/,
84161     xlink: "http:/" + "/www.w3.org/1999/xlink",
84162
84163     translateAttrs: {
84164         radius: "r",
84165         radiusX: "rx",
84166         radiusY: "ry",
84167         path: "d",
84168         lineWidth: "stroke-width",
84169         fillOpacity: "fill-opacity",
84170         strokeOpacity: "stroke-opacity",
84171         strokeLinejoin: "stroke-linejoin"
84172     },
84173     
84174     parsers: {},
84175
84176     minDefaults: {
84177         circle: {
84178             cx: 0,
84179             cy: 0,
84180             r: 0,
84181             fill: "none",
84182             stroke: null,
84183             "stroke-width": null,
84184             opacity: null,
84185             "fill-opacity": null,
84186             "stroke-opacity": null
84187         },
84188         ellipse: {
84189             cx: 0,
84190             cy: 0,
84191             rx: 0,
84192             ry: 0,
84193             fill: "none",
84194             stroke: null,
84195             "stroke-width": null,
84196             opacity: null,
84197             "fill-opacity": null,
84198             "stroke-opacity": null
84199         },
84200         rect: {
84201             x: 0,
84202             y: 0,
84203             width: 0,
84204             height: 0,
84205             rx: 0,
84206             ry: 0,
84207             fill: "none",
84208             stroke: null,
84209             "stroke-width": null,
84210             opacity: null,
84211             "fill-opacity": null,
84212             "stroke-opacity": null
84213         },
84214         text: {
84215             x: 0,
84216             y: 0,
84217             "text-anchor": "start",
84218             "font-family": null,
84219             "font-size": null,
84220             "font-weight": null,
84221             "font-style": null,
84222             fill: "#000",
84223             stroke: null,
84224             "stroke-width": null,
84225             opacity: null,
84226             "fill-opacity": null,
84227             "stroke-opacity": null
84228         },
84229         path: {
84230             d: "M0,0",
84231             fill: "none",
84232             stroke: null,
84233             "stroke-width": null,
84234             opacity: null,
84235             "fill-opacity": null,
84236             "stroke-opacity": null
84237         },
84238         image: {
84239             x: 0,
84240             y: 0,
84241             width: 0,
84242             height: 0,
84243             preserveAspectRatio: "none",
84244             opacity: null
84245         }
84246     },
84247
84248     createSvgElement: function(type, attrs) {
84249         var el = this.domRef.createElementNS("http:/" + "/www.w3.org/2000/svg", type),
84250             key;
84251         if (attrs) {
84252             for (key in attrs) {
84253                 el.setAttribute(key, String(attrs[key]));
84254             }
84255         }
84256         return el;
84257     },
84258
84259     createSpriteElement: function(sprite) {
84260         // Create svg element and append to the DOM.
84261         var el = this.createSvgElement(sprite.type);
84262         el.id = sprite.id;
84263         if (el.style) {
84264             el.style.webkitTapHighlightColor = "rgba(0,0,0,0)";
84265         }
84266         sprite.el = Ext.get(el);
84267         this.applyZIndex(sprite); //performs the insertion
84268         sprite.matrix = Ext.create('Ext.draw.Matrix');
84269         sprite.bbox = {
84270             plain: 0,
84271             transform: 0
84272         };
84273         sprite.fireEvent("render", sprite);
84274         return el;
84275     },
84276
84277     getBBox: function (sprite, isWithoutTransform) {
84278         var realPath = this["getPath" + sprite.type](sprite);
84279         if (isWithoutTransform) {
84280             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
84281             return sprite.bbox.plain;
84282         }
84283         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
84284         return sprite.bbox.transform;
84285     },
84286     
84287     getBBoxText: function (sprite) {
84288         var bbox = {},
84289             bb, height, width, i, ln, el;
84290
84291         if (sprite && sprite.el) {
84292             el = sprite.el.dom;
84293             try {
84294                 bbox = el.getBBox();
84295                 return bbox;
84296             } catch(e) {
84297                 // Firefox 3.0.x plays badly here
84298             }
84299             bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
84300             ln = el.getNumberOfChars();
84301             for (i = 0; i < ln; i++) {
84302                 bb = el.getExtentOfChar(i);
84303                 bbox.y = Math.min(bb.y, bbox.y);
84304                 height = bb.y + bb.height - bbox.y;
84305                 bbox.height = Math.max(bbox.height, height);
84306                 width = bb.x + bb.width - bbox.x;
84307                 bbox.width = Math.max(bbox.width, width);
84308             }
84309             return bbox;
84310         }
84311     },
84312
84313     hide: function() {
84314         Ext.get(this.el).hide();
84315     },
84316
84317     show: function() {
84318         Ext.get(this.el).show();
84319     },
84320
84321     hidePrim: function(sprite) {
84322         this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
84323     },
84324
84325     showPrim: function(sprite) {
84326         this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
84327     },
84328
84329     getDefs: function() {
84330         return this._defs || (this._defs = this.createSvgElement("defs"));
84331     },
84332
84333     transform: function(sprite) {
84334         var me = this,
84335             matrix = Ext.create('Ext.draw.Matrix'),
84336             transforms = sprite.transformations,
84337             transformsLength = transforms.length,
84338             i = 0,
84339             transform, type;
84340             
84341         for (; i < transformsLength; i++) {
84342             transform = transforms[i];
84343             type = transform.type;
84344             if (type == "translate") {
84345                 matrix.translate(transform.x, transform.y);
84346             }
84347             else if (type == "rotate") {
84348                 matrix.rotate(transform.degrees, transform.x, transform.y);
84349             }
84350             else if (type == "scale") {
84351                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
84352             }
84353         }
84354         sprite.matrix = matrix;
84355         sprite.el.set({transform: matrix.toSvg()});
84356     },
84357
84358     setSize: function(w, h) {
84359         var me = this,
84360             el = me.el;
84361         
84362         w = +w || me.width;
84363         h = +h || me.height;
84364         me.width = w;
84365         me.height = h;
84366
84367         el.setSize(w, h);
84368         el.set({
84369             width: w,
84370             height: h
84371         });
84372         me.callParent([w, h]);
84373     },
84374
84375     /**
84376      * Get the region for the surface's canvas area
84377      * @returns {Ext.util.Region}
84378      */
84379     getRegion: function() {
84380         // Mozilla requires using the background rect because the svg element returns an
84381         // incorrect region. Webkit gives no region for the rect and must use the svg element.
84382         var svgXY = this.el.getXY(),
84383             rectXY = this.bgRect.getXY(),
84384             max = Math.max,
84385             x = max(svgXY[0], rectXY[0]),
84386             y = max(svgXY[1], rectXY[1]);
84387         return {
84388             left: x,
84389             top: y,
84390             right: x + this.width,
84391             bottom: y + this.height
84392         };
84393     },
84394
84395     onRemove: function(sprite) {
84396         if (sprite.el) {
84397             sprite.el.remove();
84398             delete sprite.el;
84399         }
84400         this.callParent(arguments);
84401     },
84402     
84403     setViewBox: function(x, y, width, height) {
84404         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
84405             this.callParent(arguments);
84406             this.el.dom.setAttribute("viewBox", [x, y, width, height].join(" "));
84407         }
84408     },
84409
84410     render: function (container) {
84411         var me = this;
84412         if (!me.el) {
84413             var width = me.width || 10,
84414                 height = me.height || 10,
84415                 el = me.createSvgElement('svg', {
84416                     xmlns: "http:/" + "/www.w3.org/2000/svg",
84417                     version: 1.1,
84418                     width: width,
84419                     height: height
84420                 }),
84421                 defs = me.getDefs(),
84422
84423                 // Create a rect that is always the same size as the svg root; this serves 2 purposes:
84424                 // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
84425                 // use it rather than the svg element for retrieving the correct client rect of the
84426                 // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
84427                 bgRect = me.createSvgElement("rect", {
84428                     width: "100%",
84429                     height: "100%",
84430                     fill: "#000",
84431                     stroke: "none",
84432                     opacity: 0
84433                 }),
84434                 webkitRect;
84435             
84436                 if (Ext.isSafari3) {
84437                     // Rect that we will show/hide to fix old WebKit bug with rendering issues.
84438                     webkitRect = me.createSvgElement("rect", {
84439                         x: -10,
84440                         y: -10,
84441                         width: "110%",
84442                         height: "110%",
84443                         fill: "none",
84444                         stroke: "#000"
84445                     });
84446                 }
84447             el.appendChild(defs);
84448             if (Ext.isSafari3) {
84449                 el.appendChild(webkitRect);
84450             }
84451             el.appendChild(bgRect);
84452             container.appendChild(el);
84453             me.el = Ext.get(el);
84454             me.bgRect = Ext.get(bgRect);
84455             if (Ext.isSafari3) {
84456                 me.webkitRect = Ext.get(webkitRect);
84457                 me.webkitRect.hide();
84458             }
84459             me.el.on({
84460                 scope: me,
84461                 mouseup: me.onMouseUp,
84462                 mousedown: me.onMouseDown,
84463                 mouseover: me.onMouseOver,
84464                 mouseout: me.onMouseOut,
84465                 mousemove: me.onMouseMove,
84466                 mouseenter: me.onMouseEnter,
84467                 mouseleave: me.onMouseLeave,
84468                 click: me.onClick
84469             });
84470         }
84471         me.renderAll();
84472     },
84473
84474     // private
84475     onMouseEnter: function(e) {
84476         if (this.el.parent().getRegion().contains(e.getPoint())) {
84477             this.fireEvent('mouseenter', e);
84478         }
84479     },
84480
84481     // private
84482     onMouseLeave: function(e) {
84483         if (!this.el.parent().getRegion().contains(e.getPoint())) {
84484             this.fireEvent('mouseleave', e);
84485         }
84486     },
84487     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
84488     processEvent: function(name, e) {
84489         var target = e.getTarget(),
84490             surface = this.surface,
84491             sprite;
84492
84493         this.fireEvent(name, e);
84494         // We wrap text types in a tspan, sprite is the parent.
84495         if (target.nodeName == "tspan" && target.parentNode) {
84496             target = target.parentNode;
84497         }
84498         sprite = this.items.get(target.id);
84499         if (sprite) {
84500             sprite.fireEvent(name, sprite, e);
84501         }
84502     },
84503
84504     /* @private - Wrap SVG text inside a tspan to allow for line wrapping.  In addition this normallizes
84505      * the baseline for text the vertical middle of the text to be the same as VML.
84506      */
84507     tuneText: function (sprite, attrs) {
84508         var el = sprite.el.dom,
84509             tspans = [],
84510             height, tspan, text, i, ln, texts, factor;
84511
84512         if (attrs.hasOwnProperty("text")) {
84513            tspans = this.setText(sprite, attrs.text);
84514         }
84515         // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
84516         if (tspans.length) {
84517             height = this.getBBoxText(sprite).height;
84518             for (i = 0, ln = tspans.length; i < ln; i++) {
84519                 // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
84520                 // so we are going to normalize that here
84521                 factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
84522                 tspans[i].setAttribute("dy", i ? height * 1.2 : height / factor);
84523             }
84524             sprite.dirty = true;
84525         }
84526     },
84527
84528     setText: function(sprite, textString) {
84529          var me = this,
84530              el = sprite.el.dom,
84531              x = el.getAttribute("x"),
84532              tspans = [],
84533              height, tspan, text, i, ln, texts;
84534         
84535         while (el.firstChild) {
84536             el.removeChild(el.firstChild);
84537         }
84538         // Wrap each row into tspan to emulate rows
84539         texts = String(textString).split("\n");
84540         for (i = 0, ln = texts.length; i < ln; i++) {
84541             text = texts[i];
84542             if (text) {
84543                 tspan = me.createSvgElement("tspan");
84544                 tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
84545                 tspan.setAttribute("x", x);
84546                 el.appendChild(tspan);
84547                 tspans[i] = tspan;
84548             }
84549         }
84550         return tspans;
84551     },
84552
84553     renderAll: function() {
84554         this.items.each(this.renderItem, this);
84555     },
84556
84557     renderItem: function (sprite) {
84558         if (!this.el) {
84559             return;
84560         }
84561         if (!sprite.el) {
84562             this.createSpriteElement(sprite);
84563         }
84564         if (sprite.zIndexDirty) {
84565             this.applyZIndex(sprite);
84566         }
84567         if (sprite.dirty) {
84568             this.applyAttrs(sprite);
84569             this.applyTransformations(sprite);
84570         }
84571     },
84572
84573     redraw: function(sprite) {
84574         sprite.dirty = sprite.zIndexDirty = true;
84575         this.renderItem(sprite);
84576     },
84577
84578     applyAttrs: function (sprite) {
84579         var me = this,
84580             el = sprite.el,
84581             group = sprite.group,
84582             sattr = sprite.attr,
84583             parsers = me.parsers,
84584             //Safari does not handle linear gradients correctly in quirksmode
84585             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
84586             //ref: EXTJSIV-1472
84587             gradientsMap = me.gradientsMap || {},
84588             safariFix = Ext.isSafari && !Ext.isStrict,
84589             groups, i, ln, attrs, font, key, style, name, rect;
84590
84591         if (group) {
84592             groups = [].concat(group);
84593             ln = groups.length;
84594             for (i = 0; i < ln; i++) {
84595                 group = groups[i];
84596                 me.getGroup(group).add(sprite);
84597             }
84598             delete sprite.group;
84599         }
84600         attrs = me.scrubAttrs(sprite) || {};
84601
84602         // if (sprite.dirtyPath) {
84603             sprite.bbox.plain = 0;
84604             sprite.bbox.transform = 0;
84605             if (sprite.type == "circle" || sprite.type == "ellipse") {
84606                 attrs.cx = attrs.cx || attrs.x;
84607                 attrs.cy = attrs.cy || attrs.y;
84608             }
84609             else if (sprite.type == "rect") {
84610                 attrs.rx = attrs.ry = attrs.r;
84611             }
84612             else if (sprite.type == "path" && attrs.d) {
84613                 attrs.d = Ext.draw.Draw.pathToString(Ext.draw.Draw.pathToAbsolute(attrs.d));
84614             }
84615             sprite.dirtyPath = false;
84616         // }
84617         // else {
84618         //     delete attrs.d;
84619         // }
84620
84621         if (attrs['clip-rect']) {
84622             me.setClip(sprite, attrs);
84623             delete attrs['clip-rect'];
84624         }
84625         if (sprite.type == 'text' && attrs.font && sprite.dirtyFont) {
84626             el.set({ style: "font: " + attrs.font});
84627             sprite.dirtyFont = false;
84628         }
84629         if (sprite.type == "image") {
84630             el.dom.setAttributeNS(me.xlink, "href", attrs.src);
84631         }
84632         Ext.applyIf(attrs, me.minDefaults[sprite.type]);
84633
84634         if (sprite.dirtyHidden) {
84635             (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
84636             sprite.dirtyHidden = false;
84637         }
84638         for (key in attrs) {
84639             if (attrs.hasOwnProperty(key) && attrs[key] != null) {
84640                 //Safari does not handle linear gradients correctly in quirksmode
84641                 //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
84642                 //ref: EXTJSIV-1472
84643                 //if we're Safari in QuirksMode and we're applying some color attribute and the value of that
84644                 //attribute is a reference to a gradient then assign a plain color to that value instead of the gradient.
84645                 if (safariFix && ('color|stroke|fill'.indexOf(key) > -1) && (attrs[key] in gradientsMap)) {
84646                     attrs[key] = gradientsMap[attrs[key]];
84647                 }
84648                 if (key in parsers) {
84649                     el.dom.setAttribute(key, parsers[key](attrs[key], sprite, me));
84650                 } else {
84651                     el.dom.setAttribute(key, attrs[key]);
84652                 }
84653             }
84654         }
84655         
84656         if (sprite.type == 'text') {
84657             me.tuneText(sprite, attrs);
84658         }
84659
84660         //set styles
84661         style = sattr.style;
84662         if (style) {
84663             el.setStyle(style);
84664         }
84665
84666         sprite.dirty = false;
84667
84668         if (Ext.isSafari3) {
84669             // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
84670             me.webkitRect.show();
84671             setTimeout(function () {
84672                 me.webkitRect.hide();
84673             });
84674         }
84675     },
84676
84677     setClip: function(sprite, params) {
84678         var me = this,
84679             rect = params["clip-rect"],
84680             clipEl, clipPath;
84681         if (rect) {
84682             if (sprite.clip) {
84683                 sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
84684             }
84685             clipEl = me.createSvgElement('clipPath');
84686             clipPath = me.createSvgElement('rect');
84687             clipEl.id = Ext.id(null, 'ext-clip-');
84688             clipPath.setAttribute("x", rect.x);
84689             clipPath.setAttribute("y", rect.y);
84690             clipPath.setAttribute("width", rect.width);
84691             clipPath.setAttribute("height", rect.height);
84692             clipEl.appendChild(clipPath);
84693             me.getDefs().appendChild(clipEl);
84694             sprite.el.dom.setAttribute("clip-path", "url(#" + clipEl.id + ")");
84695             sprite.clip = clipPath;
84696         }
84697         // if (!attrs[key]) {
84698         //     var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, ""));
84699         //     clip && clip.parentNode.removeChild(clip);
84700         //     sprite.el.setAttribute("clip-path", "");
84701         //     delete attrss.clip;
84702         // }
84703     },
84704
84705     /**
84706      * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
84707      * @param {Ext.draw.Sprite} sprite
84708      */
84709     applyZIndex: function(sprite) {
84710         var me = this,
84711             items = me.items,
84712             idx = items.indexOf(sprite),
84713             el = sprite.el,
84714             prevEl;
84715         if (me.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect
84716             if (idx > 0) {
84717                 // Find the first previous sprite which has its DOM element created already
84718                 do {
84719                     prevEl = items.getAt(--idx).el;
84720                 } while (!prevEl && idx > 0);
84721             }
84722             el.insertAfter(prevEl || me.bgRect);
84723         }
84724         sprite.zIndexDirty = false;
84725     },
84726
84727     createItem: function (config) {
84728         var sprite = Ext.create('Ext.draw.Sprite', config);
84729         sprite.surface = this;
84730         return sprite;
84731     },
84732
84733     addGradient: function(gradient) {
84734         gradient = Ext.draw.Draw.parseGradient(gradient);
84735         var me = this,
84736             ln = gradient.stops.length,
84737             vector = gradient.vector,
84738             //Safari does not handle linear gradients correctly in quirksmode
84739             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
84740             //ref: EXTJSIV-1472
84741             usePlain = Ext.isSafari && !Ext.isStrict,
84742             gradientEl, stop, stopEl, i, gradientsMap;
84743             
84744         gradientsMap = me.gradientsMap || {};
84745         
84746         if (!usePlain) {
84747             if (gradient.type == "linear") {
84748                 gradientEl = me.createSvgElement("linearGradient");
84749                 gradientEl.setAttribute("x1", vector[0]);
84750                 gradientEl.setAttribute("y1", vector[1]);
84751                 gradientEl.setAttribute("x2", vector[2]);
84752                 gradientEl.setAttribute("y2", vector[3]);
84753             }
84754             else {
84755                 gradientEl = me.createSvgElement("radialGradient");
84756                 gradientEl.setAttribute("cx", gradient.centerX);
84757                 gradientEl.setAttribute("cy", gradient.centerY);
84758                 gradientEl.setAttribute("r", gradient.radius);
84759                 if (Ext.isNumber(gradient.focalX) && Ext.isNumber(gradient.focalY)) {
84760                     gradientEl.setAttribute("fx", gradient.focalX);
84761                     gradientEl.setAttribute("fy", gradient.focalY);
84762                 }
84763             }
84764             gradientEl.id = gradient.id;
84765             me.getDefs().appendChild(gradientEl);
84766             for (i = 0; i < ln; i++) {
84767                 stop = gradient.stops[i];
84768                 stopEl = me.createSvgElement("stop");
84769                 stopEl.setAttribute("offset", stop.offset + "%");
84770                 stopEl.setAttribute("stop-color", stop.color);
84771                 stopEl.setAttribute("stop-opacity",stop.opacity);
84772                 gradientEl.appendChild(stopEl);
84773             }
84774         } else {
84775             gradientsMap['url(#' + gradient.id + ')'] = gradient.stops[0].color;
84776         }
84777         me.gradientsMap = gradientsMap;
84778     },
84779
84780     /**
84781      * Checks if the specified CSS class exists on this element's DOM node.
84782      * @param {String} className The CSS class to check for
84783      * @return {Boolean} True if the class exists, else false
84784      */
84785     hasCls: function(sprite, className) {
84786         return className && (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
84787     },
84788
84789     addCls: function(sprite, className) {
84790         var el = sprite.el,
84791             i,
84792             len,
84793             v,
84794             cls = [],
84795             curCls =  el.getAttribute('class') || '';
84796         // Separate case is for speed
84797         if (!Ext.isArray(className)) {
84798             if (typeof className == 'string' && !this.hasCls(sprite, className)) {
84799                 el.set({ 'class': curCls + ' ' + className });
84800             }
84801         }
84802         else {
84803             for (i = 0, len = className.length; i < len; i++) {
84804                 v = className[i];
84805                 if (typeof v == 'string' && (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
84806                     cls.push(v);
84807                 }
84808             }
84809             if (cls.length) {
84810                 el.set({ 'class': ' ' + cls.join(' ') });
84811             }
84812         }
84813     },
84814
84815     removeCls: function(sprite, className) {
84816         var me = this,
84817             el = sprite.el,
84818             curCls =  el.getAttribute('class') || '',
84819             i, idx, len, cls, elClasses;
84820         if (!Ext.isArray(className)){
84821             className = [className];
84822         }
84823         if (curCls) {
84824             elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
84825             for (i = 0, len = className.length; i < len; i++) {
84826                 cls = className[i];
84827                 if (typeof cls == 'string') {
84828                     cls = cls.replace(me.trimRe, '');
84829                     idx = Ext.Array.indexOf(elClasses, cls);
84830                     if (idx != -1) {
84831                         Ext.Array.erase(elClasses, idx, 1);
84832                     }
84833                 }
84834             }
84835             el.set({ 'class': elClasses.join(' ') });
84836         }
84837     },
84838
84839     destroy: function() {
84840         var me = this;
84841         
84842         me.callParent();
84843         if (me.el) {
84844             me.el.remove();
84845         }
84846         delete me.el;
84847     }
84848 });
84849 /**
84850  * @class Ext.draw.engine.Vml
84851  * @extends Ext.draw.Surface
84852  * Provides specific methods to draw with VML.
84853  */
84854
84855 Ext.define('Ext.draw.engine.Vml', {
84856
84857     /* Begin Definitions */
84858
84859     extend: 'Ext.draw.Surface',
84860
84861     requires: ['Ext.draw.Draw', 'Ext.draw.Color', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
84862
84863     /* End Definitions */
84864
84865     engine: 'Vml',
84866
84867     map: {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
84868     bitesRe: /([clmz]),?([^clmz]*)/gi,
84869     valRe: /-?[^,\s-]+/g,
84870     fillUrlRe: /^url\(\s*['"]?([^\)]+?)['"]?\s*\)$/i,
84871     pathlike: /^(path|rect)$/,
84872     NonVmlPathRe: /[ahqstv]/ig, // Non-VML Pathing ops
84873     partialPathRe: /[clmz]/g,
84874     fontFamilyRe: /^['"]+|['"]+$/g,
84875     baseVmlCls: Ext.baseCSSPrefix + 'vml-base',
84876     vmlGroupCls: Ext.baseCSSPrefix + 'vml-group',
84877     spriteCls: Ext.baseCSSPrefix + 'vml-sprite',
84878     measureSpanCls: Ext.baseCSSPrefix + 'vml-measure-span',
84879     zoom: 21600,
84880     coordsize: 1000,
84881     coordorigin: '0 0',
84882
84883     // VML uses CSS z-index and therefore doesn't need sprites to be kept in zIndex order
84884     orderSpritesByZIndex: false,
84885
84886     // @private
84887     // Convert an SVG standard path into a VML path
84888     path2vml: function (path) {
84889         var me = this,
84890             nonVML =  me.NonVmlPathRe,
84891             map = me.map,
84892             val = me.valRe,
84893             zoom = me.zoom,
84894             bites = me.bitesRe,
84895             command = Ext.Function.bind(Ext.draw.Draw.pathToAbsolute, Ext.draw.Draw),
84896             res, pa, p, r, i, ii, j, jj;
84897         if (String(path).match(nonVML)) {
84898             command = Ext.Function.bind(Ext.draw.Draw.path2curve, Ext.draw.Draw);
84899         } else if (!String(path).match(me.partialPathRe)) {
84900             res = String(path).replace(bites, function (all, command, args) {
84901                 var vals = [],
84902                     isMove = command.toLowerCase() == "m",
84903                     res = map[command];
84904                 args.replace(val, function (value) {
84905                     if (isMove && vals[length] == 2) {
84906                         res += vals + map[command == "m" ? "l" : "L"];
84907                         vals = [];
84908                     }
84909                     vals.push(Math.round(value * zoom));
84910                 });
84911                 return res + vals;
84912             });
84913             return res;
84914         }
84915         pa = command(path);
84916         res = [];
84917         for (i = 0, ii = pa.length; i < ii; i++) {
84918             p = pa[i];
84919             r = pa[i][0].toLowerCase();
84920             if (r == "z") {
84921                 r = "x";
84922             }
84923             for (j = 1, jj = p.length; j < jj; j++) {
84924                 r += Math.round(p[j] * me.zoom) + (j != jj - 1 ? "," : "");
84925             }
84926             res.push(r);
84927         }
84928         return res.join(" ");
84929     },
84930
84931     // @private - set of attributes which need to be translated from the sprite API to the native browser API
84932     translateAttrs: {
84933         radius: "r",
84934         radiusX: "rx",
84935         radiusY: "ry",
84936         lineWidth: "stroke-width",
84937         fillOpacity: "fill-opacity",
84938         strokeOpacity: "stroke-opacity",
84939         strokeLinejoin: "stroke-linejoin"
84940     },
84941
84942     // @private - Minimun set of defaults for different types of sprites.
84943     minDefaults: {
84944         circle: {
84945             fill: "none",
84946             stroke: null,
84947             "stroke-width": null,
84948             opacity: null,
84949             "fill-opacity": null,
84950             "stroke-opacity": null
84951         },
84952         ellipse: {
84953             cx: 0,
84954             cy: 0,
84955             rx: 0,
84956             ry: 0,
84957             fill: "none",
84958             stroke: null,
84959             "stroke-width": null,
84960             opacity: null,
84961             "fill-opacity": null,
84962             "stroke-opacity": null
84963         },
84964         rect: {
84965             x: 0,
84966             y: 0,
84967             width: 0,
84968             height: 0,
84969             rx: 0,
84970             ry: 0,
84971             fill: "none",
84972             stroke: null,
84973             "stroke-width": null,
84974             opacity: null,
84975             "fill-opacity": null,
84976             "stroke-opacity": null
84977         },
84978         text: {
84979             x: 0,
84980             y: 0,
84981             "text-anchor": "start",
84982             font: '10px "Arial"',
84983             fill: "#000",
84984             stroke: null,
84985             "stroke-width": null,
84986             opacity: null,
84987             "fill-opacity": null,
84988             "stroke-opacity": null
84989         },
84990         path: {
84991             d: "M0,0",
84992             fill: "none",
84993             stroke: null,
84994             "stroke-width": null,
84995             opacity: null,
84996             "fill-opacity": null,
84997             "stroke-opacity": null
84998         },
84999         image: {
85000             x: 0,
85001             y: 0,
85002             width: 0,
85003             height: 0,
85004             preserveAspectRatio: "none",
85005             opacity: null
85006         }
85007     },
85008
85009     // private
85010     onMouseEnter: function(e) {
85011         this.fireEvent("mouseenter", e);
85012     },
85013
85014     // private
85015     onMouseLeave: function(e) {
85016         this.fireEvent("mouseleave", e);
85017     },
85018
85019     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
85020     processEvent: function(name, e) {
85021         var target = e.getTarget(),
85022             surface = this.surface,
85023             sprite;
85024         this.fireEvent(name, e);
85025         sprite = this.items.get(target.id);
85026         if (sprite) {
85027             sprite.fireEvent(name, sprite, e);
85028         }
85029     },
85030
85031     // Create the VML element/elements and append them to the DOM
85032     createSpriteElement: function(sprite) {
85033         var me = this,
85034             attr = sprite.attr,
85035             type = sprite.type,
85036             zoom = me.zoom,
85037             vml = sprite.vml || (sprite.vml = {}),
85038             round = Math.round,
85039             el = me.createNode('shape'),
85040             path, skew, textPath;
85041
85042         el.coordsize = zoom + ' ' + zoom;
85043         el.coordorigin = attr.coordorigin || "0 0";
85044         Ext.get(el).addCls(me.spriteCls);
85045         if (type == "text") {
85046             vml.path = path = me.createNode("path");
85047             path.textpathok = true;
85048             vml.textpath = textPath = me.createNode("textpath");
85049             textPath.on = true;
85050             el.appendChild(textPath);
85051             el.appendChild(path);
85052         }
85053         el.id = sprite.id;
85054         sprite.el = Ext.get(el);
85055         me.el.appendChild(el);
85056         if (type !== 'image') {
85057             skew = me.createNode("skew");
85058             skew.on = true;
85059             el.appendChild(skew);
85060             sprite.skew = skew;
85061         }
85062         sprite.matrix = Ext.create('Ext.draw.Matrix');
85063         sprite.bbox = {
85064             plain: null,
85065             transform: null
85066         };
85067         sprite.fireEvent("render", sprite);
85068         return sprite.el;
85069     },
85070
85071     // @private - Get bounding box for the sprite.  The Sprite itself has the public method.
85072     getBBox: function (sprite, isWithoutTransform) {
85073         var realPath = this["getPath" + sprite.type](sprite);
85074         if (isWithoutTransform) {
85075             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
85076             return sprite.bbox.plain;
85077         }
85078         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
85079         return sprite.bbox.transform;
85080     },
85081
85082     getBBoxText: function (sprite) {
85083         var vml = sprite.vml;
85084         return {
85085             x: vml.X + (vml.bbx || 0) - vml.W / 2,
85086             y: vml.Y - vml.H / 2,
85087             width: vml.W,
85088             height: vml.H
85089         };
85090     },
85091
85092     applyAttrs: function (sprite) {
85093         var me = this,
85094             vml = sprite.vml,
85095             group = sprite.group,
85096             spriteAttr = sprite.attr,
85097             el = sprite.el,
85098             dom = el.dom,
85099             style, name, groups, i, ln, scrubbedAttrs, font, key, bbox;
85100
85101         if (group) {
85102             groups = [].concat(group);
85103             ln = groups.length;
85104             for (i = 0; i < ln; i++) {
85105                 group = groups[i];
85106                 me.getGroup(group).add(sprite);
85107             }
85108             delete sprite.group;
85109         }
85110         scrubbedAttrs = me.scrubAttrs(sprite) || {};
85111
85112         if (sprite.zIndexDirty) {
85113             me.setZIndex(sprite);
85114         }
85115
85116         // Apply minimum default attributes
85117         Ext.applyIf(scrubbedAttrs, me.minDefaults[sprite.type]);
85118
85119         if (dom.href) {
85120             dom.href = scrubbedAttrs.href;
85121         }
85122         if (dom.title) {
85123             dom.title = scrubbedAttrs.title;
85124         }
85125         if (dom.target) {
85126             dom.target = scrubbedAttrs.target;
85127         }
85128         if (dom.cursor) {
85129             dom.cursor = scrubbedAttrs.cursor;
85130         }
85131
85132         // Change visibility
85133         if (sprite.dirtyHidden) {
85134             (scrubbedAttrs.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
85135             sprite.dirtyHidden = false;
85136         }
85137
85138         // Update path
85139         if (sprite.dirtyPath) {
85140             if (sprite.type == "circle" || sprite.type == "ellipse") {
85141                 var cx = scrubbedAttrs.x,
85142                     cy = scrubbedAttrs.y,
85143                     rx = scrubbedAttrs.rx || scrubbedAttrs.r || 0,
85144                     ry = scrubbedAttrs.ry || scrubbedAttrs.r || 0;
85145                 dom.path = Ext.String.format("ar{0},{1},{2},{3},{4},{1},{4},{1}",
85146                             Math.round((cx - rx) * me.zoom),
85147                             Math.round((cy - ry) * me.zoom),
85148                             Math.round((cx + rx) * me.zoom),
85149                             Math.round((cy + ry) * me.zoom),
85150                             Math.round(cx * me.zoom));
85151                 sprite.dirtyPath = false;
85152             }
85153             else if (sprite.type !== "text") {
85154                 sprite.attr.path = scrubbedAttrs.path = me.setPaths(sprite, scrubbedAttrs) || scrubbedAttrs.path;
85155                 dom.path = me.path2vml(scrubbedAttrs.path);
85156                 sprite.dirtyPath = false;
85157             }
85158         }
85159
85160         // Apply clipping
85161         if ("clip-rect" in scrubbedAttrs) {
85162             me.setClip(sprite, scrubbedAttrs);
85163         }
85164
85165         // Handle text (special handling required)
85166         if (sprite.type == "text") {
85167             me.setTextAttributes(sprite, scrubbedAttrs);
85168         }
85169
85170         // Handle fill and opacity
85171         if (sprite.type == 'image' || scrubbedAttrs.opacity  || scrubbedAttrs['fill-opacity'] || scrubbedAttrs.fill) {
85172             me.setFill(sprite, scrubbedAttrs);
85173         }
85174
85175         // Handle stroke (all fills require a stroke element)
85176         if (scrubbedAttrs.stroke || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
85177             me.setStroke(sprite, scrubbedAttrs);
85178         }
85179         
85180         //set styles
85181         style = spriteAttr.style;
85182         if (style) {
85183             el.setStyle(style);
85184         }
85185
85186         sprite.dirty = false;
85187     },
85188
85189     setZIndex: function(sprite) {
85190         if (sprite.el) {
85191             if (sprite.attr.zIndex != undefined) {
85192                 sprite.el.setStyle('zIndex', sprite.attr.zIndex);
85193             }
85194             sprite.zIndexDirty = false;
85195         }
85196     },
85197
85198     // Normalize all virtualized types into paths.
85199     setPaths: function(sprite, params) {
85200         var spriteAttr = sprite.attr;
85201         // Clear bbox cache
85202         sprite.bbox.plain = null;
85203         sprite.bbox.transform = null;
85204         if (sprite.type == 'circle') {
85205             spriteAttr.rx = spriteAttr.ry = params.r;
85206             return Ext.draw.Draw.ellipsePath(sprite);
85207         }
85208         else if (sprite.type == 'ellipse') {
85209             spriteAttr.rx = params.rx;
85210             spriteAttr.ry = params.ry;
85211             return Ext.draw.Draw.ellipsePath(sprite);
85212         }
85213         else if (sprite.type == 'rect' || sprite.type == 'image') {
85214             spriteAttr.rx = spriteAttr.ry = params.r;
85215             return Ext.draw.Draw.rectPath(sprite);
85216         }
85217         else if (sprite.type == 'path' && spriteAttr.path) {
85218             return Ext.draw.Draw.pathToAbsolute(spriteAttr.path);
85219         }
85220         return false;
85221     },
85222
85223     setFill: function(sprite, params) {
85224         var me = this,
85225             el = sprite.el,
85226             dom = el.dom,
85227             fillEl = dom.getElementsByTagName('fill')[0],
85228             opacity, gradient, fillUrl, rotation, angle;
85229
85230         if (fillEl) {
85231             dom.removeChild(fillEl);
85232         } else {
85233             fillEl = me.createNode('fill');
85234         }
85235         if (Ext.isArray(params.fill)) {
85236             params.fill = params.fill[0];
85237         }
85238         if (sprite.type == 'image') {
85239             fillEl.on = true;
85240             fillEl.src = params.src;
85241             fillEl.type = "tile";
85242             fillEl.rotate = true;
85243         } else if (params.fill == "none") {
85244             fillEl.on = false;
85245         } else {
85246             if (typeof params.opacity == "number") {
85247                 fillEl.opacity = params.opacity;
85248             }
85249             if (typeof params["fill-opacity"] == "number") {
85250                 fillEl.opacity = params["fill-opacity"];
85251             }
85252             fillEl.on = true;
85253             if (typeof params.fill == "string") {
85254                 fillUrl = params.fill.match(me.fillUrlRe);
85255                 if (fillUrl) {
85256                     fillUrl = fillUrl[1];
85257                     // If the URL matches one of the registered gradients, render that gradient
85258                     if (fillUrl.charAt(0) == "#") {
85259                         gradient = me.gradientsColl.getByKey(fillUrl.substring(1));
85260                     }
85261                     if (gradient) {
85262                         // VML angle is offset and inverted from standard, and must be adjusted to match rotation transform
85263                         rotation = params.rotation;
85264                         angle = -(gradient.angle + 270 + (rotation ? rotation.degrees : 0)) % 360;
85265                         // IE will flip the angle at 0 degrees...
85266                         if (angle === 0) {
85267                             angle = 180;
85268                         }
85269                         fillEl.angle = angle;
85270                         fillEl.type = "gradient";
85271                         fillEl.method = "sigma";
85272                         fillEl.colors = gradient.colors;
85273                     }
85274                     // Otherwise treat it as an image
85275                     else {
85276                         fillEl.src = fillUrl;
85277                         fillEl.type = "tile";
85278                         fillEl.rotate = true;
85279                     }
85280                 }
85281                 else {
85282                     fillEl.color = Ext.draw.Color.toHex(params.fill) || params.fill;
85283                     fillEl.src = "";
85284                     fillEl.type = "solid";
85285                 }
85286             }
85287         }
85288         dom.appendChild(fillEl);
85289     },
85290
85291     setStroke: function(sprite, params) {
85292         var me = this,
85293             el = sprite.el.dom,
85294             strokeEl = sprite.strokeEl,
85295             newStroke = false,
85296             width, opacity;
85297
85298         if (!strokeEl) {
85299             strokeEl = sprite.strokeEl = me.createNode("stroke");
85300             newStroke = true;
85301         }
85302         if (Ext.isArray(params.stroke)) {
85303             params.stroke = params.stroke[0];
85304         }
85305         if (!params.stroke || params.stroke == "none" || params.stroke == 0 || params["stroke-width"] == 0) {
85306             strokeEl.on = false;
85307         }
85308         else {
85309             strokeEl.on = true;
85310             if (params.stroke && !params.stroke.match(me.fillUrlRe)) {
85311                 // VML does NOT support a gradient stroke :(
85312                 strokeEl.color = Ext.draw.Color.toHex(params.stroke);
85313             }
85314             strokeEl.joinstyle = params["stroke-linejoin"];
85315             strokeEl.endcap = params["stroke-linecap"] || "round";
85316             strokeEl.miterlimit = params["stroke-miterlimit"] || 8;
85317             width = parseFloat(params["stroke-width"] || 1) * 0.75;
85318             opacity = params["stroke-opacity"] || 1;
85319             // VML Does not support stroke widths under 1, so we're going to fiddle with stroke-opacity instead.
85320             if (Ext.isNumber(width) && width < 1) {
85321                 strokeEl.weight = 1;
85322                 strokeEl.opacity = opacity * width;
85323             }
85324             else {
85325                 strokeEl.weight = width;
85326                 strokeEl.opacity = opacity;
85327             }
85328         }
85329         if (newStroke) {
85330             el.appendChild(strokeEl);
85331         }
85332     },
85333
85334     setClip: function(sprite, params) {
85335         var me = this,
85336             el = sprite.el,
85337             clipEl = sprite.clipEl,
85338             rect = String(params["clip-rect"]).split(me.separatorRe);
85339         if (!clipEl) {
85340             clipEl = sprite.clipEl = me.el.insertFirst(Ext.getDoc().dom.createElement("div"));
85341             clipEl.addCls(Ext.baseCSSPrefix + 'vml-sprite');
85342         }
85343         if (rect.length == 4) {
85344             rect[2] = +rect[2] + (+rect[0]);
85345             rect[3] = +rect[3] + (+rect[1]);
85346             clipEl.setStyle("clip", Ext.String.format("rect({1}px {2}px {3}px {0}px)", rect[0], rect[1], rect[2], rect[3]));
85347             clipEl.setSize(me.el.width, me.el.height);
85348         }
85349         else {
85350             clipEl.setStyle("clip", "");
85351         }
85352     },
85353
85354     setTextAttributes: function(sprite, params) {
85355         var me = this,
85356             vml = sprite.vml,
85357             textStyle = vml.textpath.style,
85358             spanCacheStyle = me.span.style,
85359             zoom = me.zoom,
85360             round = Math.round,
85361             fontObj = {
85362                 fontSize: "font-size",
85363                 fontWeight: "font-weight",
85364                 fontStyle: "font-style"
85365             },
85366             fontProp,
85367             paramProp;
85368         if (sprite.dirtyFont) {
85369             if (params.font) {
85370                 textStyle.font = spanCacheStyle.font = params.font;
85371             }
85372             if (params["font-family"]) {
85373                 textStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(me.fontFamilyRe, "") + '"';
85374                 spanCacheStyle.fontFamily = params["font-family"];
85375             }
85376
85377             for (fontProp in fontObj) {
85378                 paramProp = params[fontObj[fontProp]];
85379                 if (paramProp) {
85380                     textStyle[fontProp] = spanCacheStyle[fontProp] = paramProp;
85381                 }
85382             }
85383
85384             me.setText(sprite, params.text);
85385             
85386             if (vml.textpath.string) {
85387                 me.span.innerHTML = String(vml.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>");
85388             }
85389             vml.W = me.span.offsetWidth;
85390             vml.H = me.span.offsetHeight + 2; // TODO handle baseline differences and offset in VML Textpath
85391
85392             // text-anchor emulation
85393             if (params["text-anchor"] == "middle") {
85394                 textStyle["v-text-align"] = "center";
85395             }
85396             else if (params["text-anchor"] == "end") {
85397                 textStyle["v-text-align"] = "right";
85398                 vml.bbx = -Math.round(vml.W / 2);
85399             }
85400             else {
85401                 textStyle["v-text-align"] = "left";
85402                 vml.bbx = Math.round(vml.W / 2);
85403             }
85404         }
85405         vml.X = params.x;
85406         vml.Y = params.y;
85407         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);
85408         // Clear bbox cache
85409         sprite.bbox.plain = null;
85410         sprite.bbox.transform = null;
85411         sprite.dirtyFont = false;
85412     },
85413     
85414     setText: function(sprite, text) {
85415         sprite.vml.textpath.string = Ext.htmlDecode(text);
85416     },
85417
85418     hide: function() {
85419         this.el.hide();
85420     },
85421
85422     show: function() {
85423         this.el.show();
85424     },
85425
85426     hidePrim: function(sprite) {
85427         sprite.el.addCls(Ext.baseCSSPrefix + 'hide-visibility');
85428     },
85429
85430     showPrim: function(sprite) {
85431         sprite.el.removeCls(Ext.baseCSSPrefix + 'hide-visibility');
85432     },
85433
85434     setSize: function(width, height) {
85435         var me = this;
85436         width = width || me.width;
85437         height = height || me.height;
85438         me.width = width;
85439         me.height = height;
85440
85441         if (me.el) {
85442             // Size outer div
85443             if (width != undefined) {
85444                 me.el.setWidth(width);
85445             }
85446             if (height != undefined) {
85447                 me.el.setHeight(height);
85448             }
85449
85450             // Handle viewBox sizing
85451             me.applyViewBox();
85452
85453             me.callParent(arguments);
85454         }
85455     },
85456
85457     setViewBox: function(x, y, width, height) {
85458         this.callParent(arguments);
85459         this.viewBox = {
85460             x: x,
85461             y: y,
85462             width: width,
85463             height: height
85464         };
85465         this.applyViewBox();
85466     },
85467
85468     /**
85469      * @private Using the current viewBox property and the surface's width and height, calculate the
85470      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
85471      */
85472     applyViewBox: function() {
85473         var me = this,
85474             viewBox = me.viewBox,
85475             width = me.width,
85476             height = me.height,
85477             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
85478             relativeHeight, relativeWidth, size;
85479
85480         if (viewBox && (width || height)) {
85481             viewBoxX = viewBox.x;
85482             viewBoxY = viewBox.y;
85483             viewBoxWidth = viewBox.width;
85484             viewBoxHeight = viewBox.height;
85485             relativeHeight = height / viewBoxHeight;
85486             relativeWidth = width / viewBoxWidth;
85487
85488             if (viewBoxWidth * relativeHeight < width) {
85489                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
85490             }
85491             if (viewBoxHeight * relativeWidth < height) {
85492                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
85493             }
85494
85495             size = 1 / Math.max(viewBoxWidth / width, viewBoxHeight / height);
85496
85497             me.viewBoxShift = {
85498                 dx: -viewBoxX,
85499                 dy: -viewBoxY,
85500                 scale: size
85501             };
85502             me.items.each(function(item) {
85503                 me.transform(item);
85504             });
85505         }
85506     },
85507
85508     onAdd: function(item) {
85509         this.callParent(arguments);
85510         if (this.el) {
85511             this.renderItem(item);
85512         }
85513     },
85514
85515     onRemove: function(sprite) {
85516         if (sprite.el) {
85517             sprite.el.remove();
85518             delete sprite.el;
85519         }
85520         this.callParent(arguments);
85521     },
85522
85523     // VML Node factory method (createNode)
85524     createNode : (function () {
85525         try {
85526             var doc = Ext.getDoc().dom;
85527             if (!doc.namespaces.rvml) {
85528                 doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
85529             }
85530             return function (tagName) {
85531                 return doc.createElement("<rvml:" + tagName + ' class="rvml">');
85532             };
85533         } catch (e) {
85534             return function (tagName) {
85535                 return doc.createElement("<" + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
85536             };
85537         }
85538     })(),
85539
85540     render: function (container) {
85541         var me = this,
85542             doc = Ext.getDoc().dom;
85543
85544         if (!me.el) {
85545             var el = doc.createElement("div");
85546             me.el = Ext.get(el);
85547             me.el.addCls(me.baseVmlCls);
85548
85549             // Measuring span (offscrren)
85550             me.span = doc.createElement("span");
85551             Ext.get(me.span).addCls(me.measureSpanCls);
85552             el.appendChild(me.span);
85553             me.el.setSize(me.width || 10, me.height || 10);
85554             container.appendChild(el);
85555             me.el.on({
85556                 scope: me,
85557                 mouseup: me.onMouseUp,
85558                 mousedown: me.onMouseDown,
85559                 mouseover: me.onMouseOver,
85560                 mouseout: me.onMouseOut,
85561                 mousemove: me.onMouseMove,
85562                 mouseenter: me.onMouseEnter,
85563                 mouseleave: me.onMouseLeave,
85564                 click: me.onClick
85565             });
85566         }
85567         me.renderAll();
85568     },
85569
85570     renderAll: function() {
85571         this.items.each(this.renderItem, this);
85572     },
85573
85574     redraw: function(sprite) {
85575         sprite.dirty = true;
85576         this.renderItem(sprite);
85577     },
85578
85579     renderItem: function (sprite) {
85580         // Does the surface element exist?
85581         if (!this.el) {
85582             return;
85583         }
85584
85585         // Create sprite element if necessary
85586         if (!sprite.el) {
85587             this.createSpriteElement(sprite);
85588         }
85589
85590         if (sprite.dirty) {
85591             this.applyAttrs(sprite);
85592             if (sprite.dirtyTransform) {
85593                 this.applyTransformations(sprite);
85594             }
85595         }
85596     },
85597
85598     rotationCompensation: function (deg, dx, dy) {
85599         var matrix = Ext.create('Ext.draw.Matrix');
85600         matrix.rotate(-deg, 0.5, 0.5);
85601         return {
85602             x: matrix.x(dx, dy),
85603             y: matrix.y(dx, dy)
85604         };
85605     },
85606
85607     extractTransform: function (sprite) {
85608         var me = this,
85609             matrix = Ext.create('Ext.draw.Matrix'), scale,
85610             transformstions, tranformationsLength,
85611             transform, i = 0,
85612             shift = me.viewBoxShift;
85613
85614         for(transformstions = sprite.transformations, tranformationsLength = transformstions.length;
85615             i < tranformationsLength; i ++) {
85616             transform = transformstions[i];
85617             switch (transform.type) {
85618                 case 'translate' :
85619                     matrix.translate(transform.x, transform.y);
85620                     break;
85621                 case 'rotate':
85622                     matrix.rotate(transform.degrees, transform.x, transform.y);
85623                     break;
85624                 case 'scale':
85625                     matrix.scale(transform.x || transform.scale, transform.y || transform.scale, transform.centerX, transform.centerY);
85626                     break;
85627             }
85628         }
85629
85630         if (shift) {
85631             matrix.add(1, 0, 0, 1, shift.dx, shift.dy);
85632             matrix.prepend(shift.scale, 0, 0, shift.scale, 0, 0);
85633         }
85634         
85635         return sprite.matrix = matrix;
85636     },
85637
85638     setSimpleCoords: function(sprite, sx, sy, dx, dy, rotate) {
85639         var me = this,
85640             matrix = sprite.matrix,
85641             dom = sprite.el.dom,
85642             style = dom.style,
85643             yFlipper = 1,
85644             flip = "",
85645             fill = dom.getElementsByTagName('fill')[0],
85646             kx = me.zoom / sx,
85647             ky = me.zoom / sy,
85648             rotationCompensation;
85649         if (!sx || !sy) {
85650             return;
85651         }
85652         dom.coordsize = Math.abs(kx) + ' ' + Math.abs(ky);
85653         style.rotation = rotate * (sx * sy < 0 ? -1 : 1);
85654         if (rotate) {
85655             rotationCompensation = me.rotationCompensation(rotate, dx, dy);
85656             dx = rotationCompensation.x;
85657             dy = rotationCompensation.y;
85658         }
85659         if (sx < 0) {
85660             flip += "x"
85661         }
85662         if (sy < 0) {
85663             flip += " y";
85664             yFlipper = -1;
85665         }
85666         style.flip = flip;
85667         dom.coordorigin = (dx * -kx) + ' ' + (dy * -ky);
85668         if (fill) {
85669             dom.removeChild(fill);
85670             rotationCompensation = me.rotationCompensation(rotate, matrix.x(sprite.x, sprite.y), matrix.y(sprite.x, sprite.y));
85671             fill.position = rotationCompensation.x * yFlipper + ' ' + rotationCompensation.y * yFlipper;
85672             fill.size = sprite.width * Math.abs(sx) + ' ' + sprite.height * Math.abs(sy);
85673             dom.appendChild(fill);
85674         }
85675     },
85676
85677     transform : function (sprite) {
85678         var me = this,
85679             el = sprite.el,
85680             skew = sprite.skew,
85681             dom = el.dom,
85682             domStyle = dom.style,
85683             matrix = me.extractTransform(sprite).clone(),
85684             split, zoom = me.zoom,
85685             fill = dom.getElementsByTagName('fill')[0],
85686             isPatt = !String(sprite.fill).indexOf("url("),
85687             offset, c;
85688
85689
85690         // Hide element while we transform
85691
85692         if (sprite.type != "image" && skew && !isPatt) {
85693             // matrix transform via VML skew
85694             skew.matrix = matrix.toString();
85695             // skew.offset = '32767,1' OK
85696             // skew.offset = '32768,1' Crash
85697             // M$, R U kidding??
85698             offset = matrix.offset();
85699             if (offset[0] > 32767) {
85700                 offset[0] = 32767;
85701             } else if (offset[0] < -32768) {
85702                 offset[0] = -32768
85703             }
85704             if (offset[1] > 32767) {
85705                 offset[1] = 32767;
85706             } else if (offset[1] < -32768) {
85707                 offset[1] = -32768
85708             }
85709             skew.offset = offset;
85710         } else {
85711             if (skew) {
85712                 skew.matrix = "1 0 0 1";
85713                 skew.offset = "0 0";
85714             }
85715             split = matrix.split();
85716             if (split.isSimple) {
85717                 domStyle.filter = '';
85718                 me.setSimpleCoords(sprite, split.scaleX, split.scaleY, split.translateX, split.translateY, split.rotate / Math.PI * 180);
85719             } else {
85720                 domStyle.filter = matrix.toFilter();
85721                 var bb = me.getBBox(sprite),
85722                     dx = bb.x - sprite.x,
85723                     dy = bb.y - sprite.y;
85724                 dom.coordorigin = (dx * -zoom) + ' ' + (dy * -zoom);
85725                 if (fill) {
85726                     dom.removeChild(fill);
85727                     fill.position = dx + ' ' + dy;
85728                     fill.size = sprite.width * sprite.scale.x + ' ' + sprite.height * 1.1;
85729                     dom.appendChild(fill);
85730                 }
85731             }
85732         }
85733     },
85734
85735     createItem: function (config) {
85736         return Ext.create('Ext.draw.Sprite', config);
85737     },
85738
85739     getRegion: function() {
85740         return this.el.getRegion();
85741     },
85742
85743     addCls: function(sprite, className) {
85744         if (sprite && sprite.el) {
85745             sprite.el.addCls(className);
85746         }
85747     },
85748
85749     removeCls: function(sprite, className) {
85750         if (sprite && sprite.el) {
85751             sprite.el.removeCls(className);
85752         }
85753     },
85754
85755     /**
85756      * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
85757      * to its corresponding VML attributes and store it for later use by individual sprites.
85758      * @param {Object} gradient
85759      */
85760     addGradient: function(gradient) {
85761         var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
85762             colors = [],
85763             stops = Ext.create('Ext.util.MixedCollection');
85764
85765         // Build colors string
85766         stops.addAll(gradient.stops);
85767         stops.sortByKey("ASC", function(a, b) {
85768             a = parseInt(a, 10);
85769             b = parseInt(b, 10);
85770             return a > b ? 1 : (a < b ? -1 : 0);
85771         });
85772         stops.eachKey(function(k, v) {
85773             colors.push(k + "% " + v.color);
85774         });
85775
85776         gradients.add(gradient.id, {
85777             colors: colors.join(","),
85778             angle: gradient.angle
85779         });
85780     },
85781
85782     destroy: function() {
85783         var me = this;
85784         
85785         me.callParent(arguments);
85786         if (me.el) {
85787             me.el.remove();
85788         }
85789         delete me.el;
85790     }
85791 });
85792
85793 /**
85794  * @class Ext.fx.target.ElementCSS
85795  * @extends Ext.fx.target.Element
85796  * 
85797  * This class represents a animation target for an {@link Ext.Element} that supports CSS
85798  * based animation. In general this class will not be created directly, the {@link Ext.Element} 
85799  * will be passed to the animation and the appropriate target will be created.
85800  */
85801 Ext.define('Ext.fx.target.ElementCSS', {
85802
85803     /* Begin Definitions */
85804
85805     extend: 'Ext.fx.target.Element',
85806
85807     /* End Definitions */
85808
85809     setAttr: function(targetData, isFirstFrame) {
85810         var cssArr = {
85811                 attrs: [],
85812                 duration: [],
85813                 easing: []
85814             },
85815             ln = targetData.length,
85816             attributes,
85817             attrs,
85818             attr,
85819             easing,
85820             duration,
85821             o,
85822             i,
85823             j,
85824             ln2;
85825         for (i = 0; i < ln; i++) {
85826             attrs = targetData[i];
85827             duration = attrs.duration;
85828             easing = attrs.easing;
85829             attrs = attrs.attrs;
85830             for (attr in attrs) {
85831                 if (Ext.Array.indexOf(cssArr.attrs, attr) == -1) {
85832                     cssArr.attrs.push(attr.replace(/[A-Z]/g, function(v) {
85833                         return '-' + v.toLowerCase();
85834                     }));
85835                     cssArr.duration.push(duration + 'ms');
85836                     cssArr.easing.push(easing);
85837                 }
85838             }
85839         }
85840         attributes = cssArr.attrs.join(',');
85841         duration = cssArr.duration.join(',');
85842         easing = cssArr.easing.join(', ');
85843         for (i = 0; i < ln; i++) {
85844             attrs = targetData[i].attrs;
85845             for (attr in attrs) {
85846                 ln2 = attrs[attr].length;
85847                 for (j = 0; j < ln2; j++) {
85848                     o = attrs[attr][j];
85849                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', isFirstFrame ? '' : attributes);
85850                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', isFirstFrame ? '' : duration);
85851                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', isFirstFrame ? '' : easing);
85852                     o[0].setStyle(attr, o[1]);
85853
85854                     // Must trigger reflow to make this get used as the start point for the transition that follows
85855                     if (isFirstFrame) {
85856                         o = o[0].dom.offsetWidth;
85857                     }
85858                     else {
85859                         // Remove transition properties when completed.
85860                         o[0].on(Ext.supports.CSS3TransitionEnd, function() {
85861                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', null);
85862                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', null);
85863                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', null);
85864                         }, o[0], { single: true });
85865                     }
85866                 }
85867             }
85868         }
85869     }
85870 });
85871 /**
85872  * @class Ext.fx.target.CompositeElementCSS
85873  * @extends Ext.fx.target.CompositeElement
85874  * 
85875  * This class represents a animation target for a {@link Ext.CompositeElement}, where the
85876  * constituent elements support CSS based animation. It allows each {@link Ext.Element} in 
85877  * the group to be animated as a whole. In general this class will not be created directly, 
85878  * the {@link Ext.CompositeElement} will be passed to the animation and the appropriate target 
85879  * will be created.
85880  */
85881 Ext.define('Ext.fx.target.CompositeElementCSS', {
85882
85883     /* Begin Definitions */
85884
85885     extend: 'Ext.fx.target.CompositeElement',
85886
85887     requires: ['Ext.fx.target.ElementCSS'],
85888
85889     /* End Definitions */
85890     setAttr: function() {
85891         return Ext.fx.target.ElementCSS.prototype.setAttr.apply(this, arguments);
85892     }
85893 });
85894 /**
85895  * @class Ext.layout.container.AbstractFit
85896  * @extends Ext.layout.container.Container
85897  * @private
85898  */
85899 Ext.define('Ext.layout.container.AbstractFit', {
85900
85901     /* Begin Definitions */
85902
85903     extend: 'Ext.layout.container.Container',
85904
85905     /* End Definitions */
85906
85907     itemCls: Ext.baseCSSPrefix + 'fit-item',
85908     targetCls: Ext.baseCSSPrefix + 'layout-fit',
85909     type: 'fit'
85910 });
85911 /**
85912  * This is a base class for layouts that contain **a single item** that automatically expands to fill the layout's
85913  * container. This class is intended to be extended or created via the `layout: 'fit'`
85914  * {@link Ext.container.Container#layout} config, and should generally not need to be created directly via the new keyword.
85915  *
85916  * Fit layout does not have any direct config options (other than inherited ones). To fit a panel to a container using
85917  * Fit layout, simply set `layout: 'fit'` on the container and add a single panel to it. If the container has multiple
85918  * panels, only the first one will be displayed.
85919  *
85920  *     @example
85921  *     Ext.create('Ext.panel.Panel', {
85922  *         title: 'Fit Layout',
85923  *         width: 300,
85924  *         height: 150,
85925  *         layout:'fit',
85926  *         items: {
85927  *             title: 'Inner Panel',
85928  *             html: 'This is the inner panel content',
85929  *             bodyPadding: 20,
85930  *             border: false
85931  *         },
85932  *         renderTo: Ext.getBody()
85933  *     });
85934  */
85935 Ext.define('Ext.layout.container.Fit', {
85936
85937     /* Begin Definitions */
85938
85939     extend: 'Ext.layout.container.AbstractFit',
85940     alias: 'layout.fit',
85941     alternateClassName: 'Ext.layout.FitLayout',
85942     requires: ['Ext.layout.container.Box'],
85943
85944     /* End Definitions */
85945
85946     /**
85947      * @cfg {Object} defaultMargins
85948      * <p>If the individual contained items do not have a <tt>margins</tt>
85949      * property specified or margin specified via CSS, the default margins from this property will be
85950      * applied to each item.</p>
85951      * <br><p>This property may be specified as an object containing margins
85952      * to apply in the format:</p><pre><code>
85953 {
85954     top: (top margin),
85955     right: (right margin),
85956     bottom: (bottom margin),
85957     left: (left margin)
85958 }</code></pre>
85959      * <p>This property may also be specified as a string containing
85960      * space-separated, numeric margin values. The order of the sides associated
85961      * with each value matches the way CSS processes margin values:</p>
85962      * <div class="mdetail-params"><ul>
85963      * <li>If there is only one value, it applies to all sides.</li>
85964      * <li>If there are two values, the top and bottom borders are set to the
85965      * first value and the right and left are set to the second.</li>
85966      * <li>If there are three values, the top is set to the first value, the left
85967      * and right are set to the second, and the bottom is set to the third.</li>
85968      * <li>If there are four values, they apply to the top, right, bottom, and
85969      * left, respectively.</li>
85970      * </ul></div>
85971      * <p>Defaults to:</p><pre><code>
85972      * {top:0, right:0, bottom:0, left:0}
85973      * </code></pre>
85974      */
85975     defaultMargins: {
85976         top: 0,
85977         right: 0,
85978         bottom: 0,
85979         left: 0
85980     },
85981
85982     // @private
85983     onLayout : function() {
85984         var me = this,
85985             size,
85986             item,
85987             margins;
85988         me.callParent();
85989
85990         if (me.owner.items.length) {
85991             item = me.owner.items.get(0);
85992             margins = item.margins || me.defaultMargins;
85993             size = me.getLayoutTargetSize();
85994             size.width  -= margins.width;
85995             size.height -= margins.height;
85996             me.setItemBox(item, size);
85997
85998             // If any margins were configure either through the margins config, or in the CSS style,
85999             // Then positioning will be used.
86000             if (margins.left || margins.top) {
86001                 item.setPosition(margins.left, margins.top);
86002             }
86003         }
86004     },
86005
86006     getTargetBox : function() {
86007         return this.getLayoutTargetSize();
86008     },
86009
86010     setItemBox : function(item, box) {
86011         var me = this;
86012         if (item && box.height > 0) {
86013             if (!me.owner.isFixedWidth()) {
86014                box.width = undefined;
86015             }
86016             if (!me.owner.isFixedHeight()) {
86017                box.height = undefined;
86018             }
86019             me.setItemSize(item, box.width, box.height);
86020         }
86021     },
86022
86023     configureItem: function(item) {
86024
86025         // Card layout only controls dimensions which IT has controlled.
86026         // That calculation has to be determined at run time by examining the ownerCt's isFixedWidth()/isFixedHeight() methods
86027         item.layoutManagedHeight = 0;
86028         item.layoutManagedWidth = 0;
86029
86030         this.callParent(arguments);
86031     }
86032 }, function() {
86033     // Use Box layout's renderItem which reads CSS margins, and adds them to any configured item margins
86034     // (Defaulting to "0 0 0 0")
86035     this.prototype.renderItem = Ext.layout.container.Box.prototype.renderItem;
86036 });
86037 /**
86038  * Abstract base class for {@link Ext.layout.container.Card Card layout}.
86039  * @private
86040  */
86041 Ext.define('Ext.layout.container.AbstractCard', {
86042
86043     /* Begin Definitions */
86044
86045     extend: 'Ext.layout.container.Fit',
86046
86047     /* End Definitions */
86048
86049     type: 'card',
86050
86051     sizeAllCards: false,
86052
86053     hideInactive: true,
86054
86055     /**
86056      * @cfg {Boolean} deferredRender
86057      * True to render each contained item at the time it becomes active, false to render all contained items
86058      * as soon as the layout is rendered.  If there is a significant amount of content or
86059      * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
86060      * true might improve performance.
86061      */
86062     deferredRender : false,
86063
86064     beforeLayout: function() {
86065         var me = this;
86066         me.getActiveItem();
86067         if (me.activeItem && me.deferredRender) {
86068             me.renderItems([me.activeItem], me.getRenderTarget());
86069             return true;
86070         }
86071         else {
86072             return this.callParent(arguments);
86073         }
86074     },
86075
86076     renderChildren: function () {
86077         if (!this.deferredRender) {
86078             this.getActiveItem();
86079             this.callParent();
86080         }
86081     },
86082
86083     onLayout: function() {
86084         var me = this,
86085             activeItem = me.activeItem,
86086             items = me.getVisibleItems(),
86087             ln = items.length,
86088             targetBox = me.getTargetBox(),
86089             i, item;
86090
86091         for (i = 0; i < ln; i++) {
86092             item = items[i];
86093             me.setItemBox(item, targetBox);
86094         }
86095
86096         if (!me.firstActivated && activeItem) {
86097             if (activeItem.fireEvent('beforeactivate', activeItem) !== false) {
86098                 activeItem.fireEvent('activate', activeItem);
86099             }
86100             me.firstActivated = true;
86101         }
86102     },
86103
86104     isValidParent : function(item, target, position) {
86105         // Note: Card layout does not care about order within the target because only one is ever visible.
86106         // We only care whether the item is a direct child of the target.
86107         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
86108         return (itemEl && itemEl.parentNode === (target.dom || target)) || false;
86109     },
86110
86111     /**
86112      * Return the active (visible) component in the layout.
86113      * @returns {Ext.Component}
86114      */
86115     getActiveItem: function() {
86116         var me = this;
86117         if (!me.activeItem && me.owner) {
86118             me.activeItem = me.parseActiveItem(me.owner.activeItem);
86119         }
86120
86121         if (me.activeItem && me.owner.items.indexOf(me.activeItem) != -1) {
86122             return me.activeItem;
86123         }
86124
86125         return null;
86126     },
86127
86128     // @private
86129     parseActiveItem: function(item) {
86130         if (item && item.isComponent) {
86131             return item;
86132         }
86133         else if (typeof item == 'number' || item === undefined) {
86134             return this.getLayoutItems()[item || 0];
86135         }
86136         else {
86137             return this.owner.getComponent(item);
86138         }
86139     },
86140
86141     // @private
86142     configureItem: function(item, position) {
86143         this.callParent([item, position]);
86144         if (this.hideInactive && this.activeItem !== item) {
86145             item.hide();
86146         }
86147         else {
86148             item.show();
86149         }
86150     },
86151
86152     onRemove: function(component) {
86153         if (component === this.activeItem) {
86154             this.activeItem = null;
86155             if (this.owner.items.getCount() === 0) {
86156                 this.firstActivated = false;
86157             }
86158         }
86159     },
86160
86161     // @private
86162     getAnimation: function(newCard, owner) {
86163         var newAnim = (newCard || {}).cardSwitchAnimation;
86164         if (newAnim === false) {
86165             return false;
86166         }
86167         return newAnim || owner.cardSwitchAnimation;
86168     },
86169
86170     /**
86171      * Return the active (visible) component in the layout to the next card
86172      * @returns {Ext.Component} The next component or false.
86173      */
86174     getNext: function() {
86175         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
86176         //should come back in 4.1
86177         var wrap = arguments[0];
86178         var items = this.getLayoutItems(),
86179             index = Ext.Array.indexOf(items, this.activeItem);
86180         return items[index + 1] || (wrap ? items[0] : false);
86181     },
86182
86183     /**
86184      * Sets the active (visible) component in the layout to the next card
86185      * @return {Ext.Component} the activated component or false when nothing activated.
86186      */
86187     next: function() {
86188         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
86189         //should come back in 4.1
86190         var anim = arguments[0], wrap = arguments[1];
86191         return this.setActiveItem(this.getNext(wrap), anim);
86192     },
86193
86194     /**
86195      * Return the active (visible) component in the layout to the previous card
86196      * @returns {Ext.Component} The previous component or false.
86197      */
86198     getPrev: function() {
86199         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
86200         //should come back in 4.1
86201         var wrap = arguments[0];
86202         var items = this.getLayoutItems(),
86203             index = Ext.Array.indexOf(items, this.activeItem);
86204         return items[index - 1] || (wrap ? items[items.length - 1] : false);
86205     },
86206
86207     /**
86208      * Sets the active (visible) component in the layout to the previous card
86209      * @return {Ext.Component} the activated component or false when nothing activated.
86210      */
86211     prev: function() {
86212         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
86213         //should come back in 4.1
86214         var anim = arguments[0], wrap = arguments[1];
86215         return this.setActiveItem(this.getPrev(wrap), anim);
86216     }
86217 });
86218
86219 /**
86220  * Tracks what records are currently selected in a databound component.
86221  *
86222  * This is an abstract class and is not meant to be directly used. Databound UI widgets such as
86223  * {@link Ext.grid.Panel Grid} and {@link Ext.tree.Panel Tree} should subclass Ext.selection.Model
86224  * and provide a way to binding to the component.
86225  *
86226  * The abstract methods `onSelectChange` and `onLastFocusChanged` should be implemented in these
86227  * subclasses to update the UI widget.
86228  */
86229 Ext.define('Ext.selection.Model', {
86230     extend: 'Ext.util.Observable',
86231     alternateClassName: 'Ext.AbstractSelectionModel',
86232     requires: ['Ext.data.StoreManager'],
86233     // lastSelected
86234
86235     /**
86236      * @cfg {String} mode
86237      * Mode of selection.  Valid values are:
86238      *
86239      * - **SINGLE** - Only allows selecting one item at a time.  Use {@link #allowDeselect} to allow
86240      *   deselecting that item.  This is the default.
86241      * - **SIMPLE** - Allows simple selection of multiple items one-by-one. Each click in grid will either
86242      *   select or deselect an item.
86243      * - **MULTI** - Allows complex selection of multiple items using Ctrl and Shift keys.
86244      */
86245
86246     /**
86247      * @cfg {Boolean} allowDeselect
86248      * Allow users to deselect a record in a DataView, List or Grid.
86249      * Only applicable when the {@link #mode} is 'SINGLE'.
86250      */
86251     allowDeselect: false,
86252
86253     /**
86254      * @property {Ext.util.MixedCollection} selected
86255      * A MixedCollection that maintains all of the currently selected records. Read-only.
86256      */
86257     selected: null,
86258
86259     /**
86260      * Prune records when they are removed from the store from the selection.
86261      * This is a private flag. For an example of its usage, take a look at
86262      * Ext.selection.TreeModel.
86263      * @private
86264      */
86265     pruneRemoved: true,
86266
86267     constructor: function(cfg) {
86268         var me = this;
86269
86270         cfg = cfg || {};
86271         Ext.apply(me, cfg);
86272
86273         me.addEvents(
86274             /**
86275              * @event
86276              * Fired after a selection change has occurred
86277              * @param {Ext.selection.Model} this
86278              * @param {Ext.data.Model[]} selected The selected records
86279              */
86280             'selectionchange'
86281         );
86282
86283         me.modes = {
86284             SINGLE: true,
86285             SIMPLE: true,
86286             MULTI: true
86287         };
86288
86289         // sets this.selectionMode
86290         me.setSelectionMode(cfg.mode || me.mode);
86291
86292         // maintains the currently selected records.
86293         me.selected = Ext.create('Ext.util.MixedCollection');
86294
86295         me.callParent(arguments);
86296     },
86297
86298     // binds the store to the selModel.
86299     bind : function(store, initial){
86300         var me = this;
86301
86302         if(!initial && me.store){
86303             if(store !== me.store && me.store.autoDestroy){
86304                 me.store.destroyStore();
86305             }else{
86306                 me.store.un("add", me.onStoreAdd, me);
86307                 me.store.un("clear", me.onStoreClear, me);
86308                 me.store.un("remove", me.onStoreRemove, me);
86309                 me.store.un("update", me.onStoreUpdate, me);
86310             }
86311         }
86312         if(store){
86313             store = Ext.data.StoreManager.lookup(store);
86314             store.on({
86315                 add: me.onStoreAdd,
86316                 clear: me.onStoreClear,
86317                 remove: me.onStoreRemove,
86318                 update: me.onStoreUpdate,
86319                 scope: me
86320             });
86321         }
86322         me.store = store;
86323         if(store && !initial) {
86324             me.refresh();
86325         }
86326     },
86327
86328     /**
86329      * Selects all records in the view.
86330      * @param {Boolean} suppressEvent True to suppress any select events
86331      */
86332     selectAll: function(suppressEvent) {
86333         var me = this,
86334             selections = me.store.getRange(),
86335             i = 0,
86336             len = selections.length,
86337             start = me.getSelection().length;
86338
86339         me.bulkChange = true;
86340         for (; i < len; i++) {
86341             me.doSelect(selections[i], true, suppressEvent);
86342         }
86343         delete me.bulkChange;
86344         // fire selection change only if the number of selections differs
86345         me.maybeFireSelectionChange(me.getSelection().length !== start);
86346     },
86347
86348     /**
86349      * Deselects all records in the view.
86350      * @param {Boolean} suppressEvent True to suppress any deselect events
86351      */
86352     deselectAll: function(suppressEvent) {
86353         var me = this,
86354             selections = me.getSelection(),
86355             i = 0,
86356             len = selections.length,
86357             start = me.getSelection().length;
86358
86359         me.bulkChange = true;
86360         for (; i < len; i++) {
86361             me.doDeselect(selections[i], suppressEvent);
86362         }
86363         delete me.bulkChange;
86364         // fire selection change only if the number of selections differs
86365         me.maybeFireSelectionChange(me.getSelection().length !== start);
86366     },
86367
86368     // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
86369     // selection modes. Requires that an event be passed so that we can know
86370     // if user held ctrl or shift.
86371     selectWithEvent: function(record, e, keepExisting) {
86372         var me = this;
86373
86374         switch (me.selectionMode) {
86375             case 'MULTI':
86376                 if (e.ctrlKey && me.isSelected(record)) {
86377                     me.doDeselect(record, false);
86378                 } else if (e.shiftKey && me.lastFocused) {
86379                     me.selectRange(me.lastFocused, record, e.ctrlKey);
86380                 } else if (e.ctrlKey) {
86381                     me.doSelect(record, true, false);
86382                 } else if (me.isSelected(record) && !e.shiftKey && !e.ctrlKey && me.selected.getCount() > 1) {
86383                     me.doSelect(record, keepExisting, false);
86384                 } else {
86385                     me.doSelect(record, false);
86386                 }
86387                 break;
86388             case 'SIMPLE':
86389                 if (me.isSelected(record)) {
86390                     me.doDeselect(record);
86391                 } else {
86392                     me.doSelect(record, true);
86393                 }
86394                 break;
86395             case 'SINGLE':
86396                 // if allowDeselect is on and this record isSelected, deselect it
86397                 if (me.allowDeselect && me.isSelected(record)) {
86398                     me.doDeselect(record);
86399                 // select the record and do NOT maintain existing selections
86400                 } else {
86401                     me.doSelect(record, false);
86402                 }
86403                 break;
86404         }
86405     },
86406
86407     /**
86408      * Selects a range of rows if the selection model {@link #isLocked is not locked}.
86409      * All rows in between startRow and endRow are also selected.
86410      * @param {Ext.data.Model/Number} startRow The record or index of the first row in the range
86411      * @param {Ext.data.Model/Number} endRow The record or index of the last row in the range
86412      * @param {Boolean} [keepExisting] True to retain existing selections
86413      */
86414     selectRange : function(startRow, endRow, keepExisting, dir){
86415         var me = this,
86416             store = me.store,
86417             selectedCount = 0,
86418             i,
86419             tmp,
86420             dontDeselect,
86421             records = [];
86422
86423         if (me.isLocked()){
86424             return;
86425         }
86426
86427         if (!keepExisting) {
86428             me.deselectAll(true);
86429         }
86430
86431         if (!Ext.isNumber(startRow)) {
86432             startRow = store.indexOf(startRow);
86433         }
86434         if (!Ext.isNumber(endRow)) {
86435             endRow = store.indexOf(endRow);
86436         }
86437
86438         // swap values
86439         if (startRow > endRow){
86440             tmp = endRow;
86441             endRow = startRow;
86442             startRow = tmp;
86443         }
86444
86445         for (i = startRow; i <= endRow; i++) {
86446             if (me.isSelected(store.getAt(i))) {
86447                 selectedCount++;
86448             }
86449         }
86450
86451         if (!dir) {
86452             dontDeselect = -1;
86453         } else {
86454             dontDeselect = (dir == 'up') ? startRow : endRow;
86455         }
86456
86457         for (i = startRow; i <= endRow; i++){
86458             if (selectedCount == (endRow - startRow + 1)) {
86459                 if (i != dontDeselect) {
86460                     me.doDeselect(i, true);
86461                 }
86462             } else {
86463                 records.push(store.getAt(i));
86464             }
86465         }
86466         me.doMultiSelect(records, true);
86467     },
86468
86469     /**
86470      * Selects a record instance by record instance or index.
86471      * @param {Ext.data.Model[]/Number} records An array of records or an index
86472      * @param {Boolean} [keepExisting] True to retain existing selections
86473      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
86474      */
86475     select: function(records, keepExisting, suppressEvent) {
86476         // Automatically selecting eg store.first() or store.last() will pass undefined, so that must just return;
86477         if (Ext.isDefined(records)) {
86478             this.doSelect(records, keepExisting, suppressEvent);
86479         }
86480     },
86481
86482     /**
86483      * Deselects a record instance by record instance or index.
86484      * @param {Ext.data.Model[]/Number} records An array of records or an index
86485      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
86486      */
86487     deselect: function(records, suppressEvent) {
86488         this.doDeselect(records, suppressEvent);
86489     },
86490
86491     doSelect: function(records, keepExisting, suppressEvent) {
86492         var me = this,
86493             record;
86494
86495         if (me.locked) {
86496             return;
86497         }
86498         if (typeof records === "number") {
86499             records = [me.store.getAt(records)];
86500         }
86501         if (me.selectionMode == "SINGLE" && records) {
86502             record = records.length ? records[0] : records;
86503             me.doSingleSelect(record, suppressEvent);
86504         } else {
86505             me.doMultiSelect(records, keepExisting, suppressEvent);
86506         }
86507     },
86508
86509     doMultiSelect: function(records, keepExisting, suppressEvent) {
86510         var me = this,
86511             selected = me.selected,
86512             change = false,
86513             i = 0,
86514             len, record;
86515
86516         if (me.locked) {
86517             return;
86518         }
86519
86520
86521         records = !Ext.isArray(records) ? [records] : records;
86522         len = records.length;
86523         if (!keepExisting && selected.getCount() > 0) {
86524             if (me.doDeselect(me.getSelection(), suppressEvent) === false) {
86525                 return;
86526             }
86527             // TODO - coalesce the selectionchange event in deselect w/the one below...
86528         }
86529
86530         function commit () {
86531             selected.add(record);
86532             change = true;
86533         }
86534
86535         for (; i < len; i++) {
86536             record = records[i];
86537             if (keepExisting && me.isSelected(record)) {
86538                 continue;
86539             }
86540             me.lastSelected = record;
86541
86542             me.onSelectChange(record, true, suppressEvent, commit);
86543         }
86544         me.setLastFocused(record, suppressEvent);
86545         // fire selchange if there was a change and there is no suppressEvent flag
86546         me.maybeFireSelectionChange(change && !suppressEvent);
86547     },
86548
86549     // records can be an index, a record or an array of records
86550     doDeselect: function(records, suppressEvent) {
86551         var me = this,
86552             selected = me.selected,
86553             i = 0,
86554             len, record,
86555             attempted = 0,
86556             accepted = 0;
86557
86558         if (me.locked) {
86559             return false;
86560         }
86561
86562         if (typeof records === "number") {
86563             records = [me.store.getAt(records)];
86564         } else if (!Ext.isArray(records)) {
86565             records = [records];
86566         }
86567
86568         function commit () {
86569             ++accepted;
86570             selected.remove(record);
86571         }
86572
86573         len = records.length;
86574
86575         for (; i < len; i++) {
86576             record = records[i];
86577             if (me.isSelected(record)) {
86578                 if (me.lastSelected == record) {
86579                     me.lastSelected = selected.last();
86580                 }
86581                 ++attempted;
86582                 me.onSelectChange(record, false, suppressEvent, commit);
86583             }
86584         }
86585
86586         // fire selchange if there was a change and there is no suppressEvent flag
86587         me.maybeFireSelectionChange(accepted > 0 && !suppressEvent);
86588         return accepted === attempted;
86589     },
86590
86591     doSingleSelect: function(record, suppressEvent) {
86592         var me = this,
86593             changed = false,
86594             selected = me.selected;
86595
86596         if (me.locked) {
86597             return;
86598         }
86599         // already selected.
86600         // should we also check beforeselect?
86601         if (me.isSelected(record)) {
86602             return;
86603         }
86604
86605         function commit () {
86606             me.bulkChange = true;
86607             if (selected.getCount() > 0 && me.doDeselect(me.lastSelected, suppressEvent) === false) {
86608                 delete me.bulkChange;
86609                 return false;
86610             }
86611             delete me.bulkChange;
86612
86613             selected.add(record);
86614             me.lastSelected = record;
86615             changed = true;
86616         }
86617
86618         me.onSelectChange(record, true, suppressEvent, commit);
86619
86620         if (changed) {
86621             if (!suppressEvent) {
86622                 me.setLastFocused(record);
86623             }
86624             me.maybeFireSelectionChange(!suppressEvent);
86625         }
86626     },
86627
86628     /**
86629      * Sets a record as the last focused record. This does NOT mean
86630      * that the record has been selected.
86631      * @param {Ext.data.Model} record
86632      */
86633     setLastFocused: function(record, supressFocus) {
86634         var me = this,
86635             recordBeforeLast = me.lastFocused;
86636         me.lastFocused = record;
86637         me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
86638     },
86639
86640     /**
86641      * Determines if this record is currently focused.
86642      * @param {Ext.data.Model} record
86643      */
86644     isFocused: function(record) {
86645         return record === this.getLastFocused();
86646     },
86647
86648
86649     // fire selection change as long as true is not passed
86650     // into maybeFireSelectionChange
86651     maybeFireSelectionChange: function(fireEvent) {
86652         var me = this;
86653         if (fireEvent && !me.bulkChange) {
86654             me.fireEvent('selectionchange', me, me.getSelection());
86655         }
86656     },
86657
86658     /**
86659      * Returns the last selected record.
86660      */
86661     getLastSelected: function() {
86662         return this.lastSelected;
86663     },
86664
86665     getLastFocused: function() {
86666         return this.lastFocused;
86667     },
86668
86669     /**
86670      * Returns an array of the currently selected records.
86671      * @return {Ext.data.Model[]} The selected records
86672      */
86673     getSelection: function() {
86674         return this.selected.getRange();
86675     },
86676
86677     /**
86678      * Returns the current selectionMode.
86679      * @return {String} The selectionMode: 'SINGLE', 'MULTI' or 'SIMPLE'.
86680      */
86681     getSelectionMode: function() {
86682         return this.selectionMode;
86683     },
86684
86685     /**
86686      * Sets the current selectionMode.
86687      * @param {String} selModel 'SINGLE', 'MULTI' or 'SIMPLE'.
86688      */
86689     setSelectionMode: function(selMode) {
86690         selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
86691         // set to mode specified unless it doesnt exist, in that case
86692         // use single.
86693         this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
86694     },
86695
86696     /**
86697      * Returns true if the selections are locked.
86698      * @return {Boolean}
86699      */
86700     isLocked: function() {
86701         return this.locked;
86702     },
86703
86704     /**
86705      * Locks the current selection and disables any changes from happening to the selection.
86706      * @param {Boolean} locked  True to lock, false to unlock.
86707      */
86708     setLocked: function(locked) {
86709         this.locked = !!locked;
86710     },
86711
86712     /**
86713      * Returns true if the specified row is selected.
86714      * @param {Ext.data.Model/Number} record The record or index of the record to check
86715      * @return {Boolean}
86716      */
86717     isSelected: function(record) {
86718         record = Ext.isNumber(record) ? this.store.getAt(record) : record;
86719         return this.selected.indexOf(record) !== -1;
86720     },
86721
86722     /**
86723      * Returns true if there are any a selected records.
86724      * @return {Boolean}
86725      */
86726     hasSelection: function() {
86727         return this.selected.getCount() > 0;
86728     },
86729
86730     refresh: function() {
86731         var me = this,
86732             toBeSelected = [],
86733             oldSelections = me.getSelection(),
86734             len = oldSelections.length,
86735             selection,
86736             change,
86737             i = 0,
86738             lastFocused = this.getLastFocused();
86739
86740         // check to make sure that there are no records
86741         // missing after the refresh was triggered, prune
86742         // them from what is to be selected if so
86743         for (; i < len; i++) {
86744             selection = oldSelections[i];
86745             if (!this.pruneRemoved || me.store.indexOf(selection) !== -1) {
86746                 toBeSelected.push(selection);
86747             }
86748         }
86749
86750         // there was a change from the old selected and
86751         // the new selection
86752         if (me.selected.getCount() != toBeSelected.length) {
86753             change = true;
86754         }
86755
86756         me.clearSelections();
86757
86758         if (me.store.indexOf(lastFocused) !== -1) {
86759             // restore the last focus but supress restoring focus
86760             this.setLastFocused(lastFocused, true);
86761         }
86762
86763         if (toBeSelected.length) {
86764             // perform the selection again
86765             me.doSelect(toBeSelected, false, true);
86766         }
86767
86768         me.maybeFireSelectionChange(change);
86769     },
86770
86771     /**
86772      * A fast reset of the selections without firing events, updating the ui, etc.
86773      * For private usage only.
86774      * @private
86775      */
86776     clearSelections: function() {
86777         // reset the entire selection to nothing
86778         this.selected.clear();
86779         this.lastSelected = null;
86780         this.setLastFocused(null);
86781     },
86782
86783     // when a record is added to a store
86784     onStoreAdd: function() {
86785
86786     },
86787
86788     // when a store is cleared remove all selections
86789     // (if there were any)
86790     onStoreClear: function() {
86791         if (this.selected.getCount > 0) {
86792             this.clearSelections();
86793             this.maybeFireSelectionChange(true);
86794         }
86795     },
86796
86797     // prune records from the SelectionModel if
86798     // they were selected at the time they were
86799     // removed.
86800     onStoreRemove: function(store, record) {
86801         var me = this,
86802             selected = me.selected;
86803
86804         if (me.locked || !me.pruneRemoved) {
86805             return;
86806         }
86807
86808         if (selected.remove(record)) {
86809             if (me.lastSelected == record) {
86810                 me.lastSelected = null;
86811             }
86812             if (me.getLastFocused() == record) {
86813                 me.setLastFocused(null);
86814             }
86815             me.maybeFireSelectionChange(true);
86816         }
86817     },
86818
86819     /**
86820      * Returns the count of selected records.
86821      * @return {Number} The number of selected records
86822      */
86823     getCount: function() {
86824         return this.selected.getCount();
86825     },
86826
86827     // cleanup.
86828     destroy: function() {
86829
86830     },
86831
86832     // if records are updated
86833     onStoreUpdate: function() {
86834
86835     },
86836
86837     // @abstract
86838     onSelectChange: function(record, isSelected, suppressEvent) {
86839
86840     },
86841
86842     // @abstract
86843     onLastFocusChanged: function(oldFocused, newFocused) {
86844
86845     },
86846
86847     // @abstract
86848     onEditorKey: function(field, e) {
86849
86850     },
86851
86852     // @abstract
86853     bindComponent: function(cmp) {
86854
86855     }
86856 });
86857 /**
86858  * @class Ext.selection.DataViewModel
86859  * @ignore
86860  */
86861 Ext.define('Ext.selection.DataViewModel', {
86862     extend: 'Ext.selection.Model',
86863
86864     requires: ['Ext.util.KeyNav'],
86865
86866     deselectOnContainerClick: true,
86867
86868     /**
86869      * @cfg {Boolean} enableKeyNav
86870      *
86871      * Turns on/off keyboard navigation within the DataView.
86872      */
86873     enableKeyNav: true,
86874
86875     constructor: function(cfg){
86876         this.addEvents(
86877             /**
86878              * @event beforedeselect
86879              * Fired before a record is deselected. If any listener returns false, the
86880              * deselection is cancelled.
86881              * @param {Ext.selection.DataViewModel} this
86882              * @param {Ext.data.Model} record The deselected record
86883              */
86884             'beforedeselect',
86885
86886             /**
86887              * @event beforeselect
86888              * Fired before a record is selected. If any listener returns false, the
86889              * selection is cancelled.
86890              * @param {Ext.selection.DataViewModel} this
86891              * @param {Ext.data.Model} record The selected record
86892              */
86893             'beforeselect',
86894
86895             /**
86896              * @event deselect
86897              * Fired after a record is deselected
86898              * @param {Ext.selection.DataViewModel} this
86899              * @param  {Ext.data.Model} record The deselected record
86900              */
86901             'deselect',
86902
86903             /**
86904              * @event select
86905              * Fired after a record is selected
86906              * @param {Ext.selection.DataViewModel} this
86907              * @param  {Ext.data.Model} record The selected record
86908              */
86909             'select'
86910         );
86911         this.callParent(arguments);
86912     },
86913
86914     bindComponent: function(view) {
86915         var me = this,
86916             eventListeners = {
86917                 refresh: me.refresh,
86918                 scope: me
86919             };
86920
86921         me.view = view;
86922         me.bind(view.getStore());
86923
86924         view.on(view.triggerEvent, me.onItemClick, me);
86925         view.on(view.triggerCtEvent, me.onContainerClick, me);
86926
86927         view.on(eventListeners);
86928
86929         if (me.enableKeyNav) {
86930             me.initKeyNav(view);
86931         }
86932     },
86933
86934     onItemClick: function(view, record, item, index, e) {
86935         this.selectWithEvent(record, e);
86936     },
86937
86938     onContainerClick: function() {
86939         if (this.deselectOnContainerClick) {
86940             this.deselectAll();
86941         }
86942     },
86943
86944     initKeyNav: function(view) {
86945         var me = this;
86946
86947         if (!view.rendered) {
86948             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
86949             return;
86950         }
86951
86952         view.el.set({
86953             tabIndex: -1
86954         });
86955         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
86956             down: Ext.pass(me.onNavKey, [1], me),
86957             right: Ext.pass(me.onNavKey, [1], me),
86958             left: Ext.pass(me.onNavKey, [-1], me),
86959             up: Ext.pass(me.onNavKey, [-1], me),
86960             scope: me
86961         });
86962     },
86963
86964     onNavKey: function(step) {
86965         step = step || 1;
86966         var me = this,
86967             view = me.view,
86968             selected = me.getSelection()[0],
86969             numRecords = me.view.store.getCount(),
86970             idx;
86971
86972         if (selected) {
86973             idx = view.indexOf(view.getNode(selected)) + step;
86974         } else {
86975             idx = 0;
86976         }
86977
86978         if (idx < 0) {
86979             idx = numRecords - 1;
86980         } else if (idx >= numRecords) {
86981             idx = 0;
86982         }
86983
86984         me.select(idx);
86985     },
86986
86987     // Allow the DataView to update the ui
86988     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
86989         var me = this,
86990             view = me.view,
86991             eventName = isSelected ? 'select' : 'deselect';
86992
86993         if ((suppressEvent || me.fireEvent('before' + eventName, me, record)) !== false &&
86994                 commitFn() !== false) {
86995
86996             if (isSelected) {
86997                 view.onItemSelect(record);
86998             } else {
86999                 view.onItemDeselect(record);
87000             }
87001
87002             if (!suppressEvent) {
87003                 me.fireEvent(eventName, me, record);
87004             }
87005         }
87006     },
87007     
87008     destroy: function(){
87009         Ext.destroy(this.keyNav);
87010         this.callParent();
87011     }
87012 });
87013
87014 /**
87015  * A Provider implementation which saves and retrieves state via cookies. The CookieProvider supports the usual cookie
87016  * options, such as:
87017  *
87018  * - {@link #path}
87019  * - {@link #expires}
87020  * - {@link #domain}
87021  * - {@link #secure}
87022  *
87023  * Example:
87024  *
87025  *     Ext.create('Ext.state.CookieProvider', {
87026  *         path: "/cgi-bin/",
87027  *         expires: new Date(new Date().getTime()+(1000*60*60*24*30)), //30 days
87028  *         domain: "sencha.com"
87029  *     });
87030  *
87031  *     Ext.state.Manager.setProvider(cp);
87032  *
87033  * @constructor
87034  * Creates a new CookieProvider.
87035  * @param {Object} config (optional) Config object.
87036  * @return {Object}
87037  */
87038 Ext.define('Ext.state.CookieProvider', {
87039     extend: 'Ext.state.Provider',
87040
87041     /**
87042      * @cfg {String} path
87043      * The path for which the cookie is active. Defaults to root '/' which makes it active for all pages in the site.
87044      */
87045
87046     /**
87047      * @cfg {Date} expires
87048      * The cookie expiration date. Defaults to 7 days from now.
87049      */
87050
87051     /**
87052      * @cfg {String} domain
87053      * The domain to save the cookie for. Note that you cannot specify a different domain than your page is on, but you can
87054      * specify a sub-domain, or simply the domain itself like 'sencha.com' to include all sub-domains if you need to access
87055      * cookies across different sub-domains. Defaults to null which uses the same domain the page is running on including
87056      * the 'www' like 'www.sencha.com'.
87057      */
87058
87059     /**
87060      * @cfg {Boolean} [secure=false]
87061      * True if the site is using SSL
87062      */
87063
87064     /**
87065      * Creates a new CookieProvider.
87066      * @param {Object} [config] Config object.
87067      */
87068     constructor : function(config){
87069         var me = this;
87070         me.path = "/";
87071         me.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
87072         me.domain = null;
87073         me.secure = false;
87074         me.callParent(arguments);
87075         me.state = me.readCookies();
87076     },
87077
87078     // private
87079     set : function(name, value){
87080         var me = this;
87081
87082         if(typeof value == "undefined" || value === null){
87083             me.clear(name);
87084             return;
87085         }
87086         me.setCookie(name, value);
87087         me.callParent(arguments);
87088     },
87089
87090     // private
87091     clear : function(name){
87092         this.clearCookie(name);
87093         this.callParent(arguments);
87094     },
87095
87096     // private
87097     readCookies : function(){
87098         var cookies = {},
87099             c = document.cookie + ";",
87100             re = /\s?(.*?)=(.*?);/g,
87101             prefix = this.prefix,
87102             len = prefix.length,
87103             matches,
87104             name,
87105             value;
87106
87107         while((matches = re.exec(c)) != null){
87108             name = matches[1];
87109             value = matches[2];
87110             if (name && name.substring(0, len) == prefix){
87111                 cookies[name.substr(len)] = this.decodeValue(value);
87112             }
87113         }
87114         return cookies;
87115     },
87116
87117     // private
87118     setCookie : function(name, value){
87119         var me = this;
87120
87121         document.cookie = me.prefix + name + "=" + me.encodeValue(value) +
87122            ((me.expires == null) ? "" : ("; expires=" + me.expires.toGMTString())) +
87123            ((me.path == null) ? "" : ("; path=" + me.path)) +
87124            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
87125            ((me.secure == true) ? "; secure" : "");
87126     },
87127
87128     // private
87129     clearCookie : function(name){
87130         var me = this;
87131
87132         document.cookie = me.prefix + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
87133            ((me.path == null) ? "" : ("; path=" + me.path)) +
87134            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
87135            ((me.secure == true) ? "; secure" : "");
87136     }
87137 });
87138
87139 /**
87140  * @class Ext.state.LocalStorageProvider
87141  * @extends Ext.state.Provider
87142  * A Provider implementation which saves and retrieves state via the HTML5 localStorage object.
87143  * If the browser does not support local storage, an exception will be thrown upon instantiating
87144  * this class.
87145  */
87146
87147 Ext.define('Ext.state.LocalStorageProvider', {
87148     /* Begin Definitions */
87149     
87150     extend: 'Ext.state.Provider',
87151     
87152     alias: 'state.localstorage',
87153     
87154     /* End Definitions */
87155    
87156     constructor: function(){
87157         var me = this;
87158         me.callParent(arguments);
87159         me.store = me.getStorageObject();
87160         me.state = me.readLocalStorage();
87161     },
87162     
87163     readLocalStorage: function(){
87164         var store = this.store,
87165             i = 0,
87166             len = store.length,
87167             prefix = this.prefix,
87168             prefixLen = prefix.length,
87169             data = {},
87170             key;
87171             
87172         for (; i < len; ++i) {
87173             key = store.key(i);
87174             if (key.substring(0, prefixLen) == prefix) {
87175                 data[key.substr(prefixLen)] = this.decodeValue(store.getItem(key));
87176             }            
87177         }
87178         return data;
87179     },
87180     
87181     set : function(name, value){
87182         var me = this;
87183         
87184         me.clear(name);
87185         if (typeof value == "undefined" || value === null) {
87186             return;
87187         }
87188         me.store.setItem(me.prefix + name, me.encodeValue(value));
87189         me.callParent(arguments);
87190     },
87191
87192     // private
87193     clear : function(name){
87194         this.store.removeItem(this.prefix + name);
87195         this.callParent(arguments);
87196     },
87197     
87198     getStorageObject: function(){
87199         try {
87200             var supports = 'localStorage' in window && window['localStorage'] !== null;
87201             if (supports) {
87202                 return window.localStorage;
87203             }
87204         } catch (e) {
87205             return false;
87206         }
87207         Ext.Error.raise('LocalStorage is not supported by the current browser');
87208     }    
87209 });
87210
87211 /**
87212  * Represents a 2D point with x and y properties, useful for comparison and instantiation
87213  * from an event:
87214  *
87215  *     var point = Ext.util.Point.fromEvent(e);
87216  *
87217  */
87218 Ext.define('Ext.util.Point', {
87219
87220     /* Begin Definitions */
87221     extend: 'Ext.util.Region',
87222
87223     statics: {
87224
87225         /**
87226          * Returns a new instance of Ext.util.Point base on the pageX / pageY values of the given event
87227          * @static
87228          * @param {Event} e The event
87229          * @return {Ext.util.Point}
87230          */
87231         fromEvent: function(e) {
87232             e = (e.changedTouches && e.changedTouches.length > 0) ? e.changedTouches[0] : e;
87233             return new this(e.pageX, e.pageY);
87234         }
87235     },
87236
87237     /* End Definitions */
87238
87239     /**
87240      * Creates a point from two coordinates.
87241      * @param {Number} x X coordinate.
87242      * @param {Number} y Y coordinate.
87243      */
87244     constructor: function(x, y) {
87245         this.callParent([y, x, y, x]);
87246     },
87247
87248     /**
87249      * Returns a human-eye-friendly string that represents this point,
87250      * useful for debugging
87251      * @return {String}
87252      */
87253     toString: function() {
87254         return "Point[" + this.x + "," + this.y + "]";
87255     },
87256
87257     /**
87258      * Compare this point and another point
87259      * @param {Ext.util.Point/Object} The point to compare with, either an instance
87260      * of Ext.util.Point or an object with left and top properties
87261      * @return {Boolean} Returns whether they are equivalent
87262      */
87263     equals: function(p) {
87264         return (this.x == p.x && this.y == p.y);
87265     },
87266
87267     /**
87268      * Whether the given point is not away from this point within the given threshold amount.
87269      * @param {Ext.util.Point/Object} p The point to check with, either an instance
87270      * of Ext.util.Point or an object with left and top properties
87271      * @param {Object/Number} threshold Can be either an object with x and y properties or a number
87272      * @return {Boolean}
87273      */
87274     isWithin: function(p, threshold) {
87275         if (!Ext.isObject(threshold)) {
87276             threshold = {
87277                 x: threshold,
87278                 y: threshold
87279             };
87280         }
87281
87282         return (this.x <= p.x + threshold.x && this.x >= p.x - threshold.x &&
87283                 this.y <= p.y + threshold.y && this.y >= p.y - threshold.y);
87284     },
87285
87286     /**
87287      * Compare this point with another point when the x and y values of both points are rounded. E.g:
87288      * [100.3,199.8] will equals to [100, 200]
87289      * @param {Ext.util.Point/Object} p The point to compare with, either an instance
87290      * of Ext.util.Point or an object with x and y properties
87291      * @return {Boolean}
87292      */
87293     roundedEquals: function(p) {
87294         return (Math.round(this.x) == Math.round(p.x) && Math.round(this.y) == Math.round(p.y));
87295     }
87296 }, function() {
87297     /**
87298      * @method
87299      * Alias for {@link #translateBy}
87300      * @alias Ext.util.Region#translateBy
87301      */
87302     this.prototype.translate = Ext.util.Region.prototype.translateBy;
87303 });
87304
87305 /**
87306  * @class Ext.LoadMask
87307  * <p>A modal, floating Component which may be shown above a specified {@link Ext.core.Element Element}, or a specified
87308  * {@link Ext.Component Component} while loading data. When shown, the configured owning Element or Component will
87309  * be covered with a modality mask, and the LoadMask's {@link #msg} will be displayed centered, accompanied by a spinner image.</p>
87310  * <p>If the {@link #store} config option is specified, the masking will be automatically shown and then hidden synchronized with
87311  * the Store's loading process.</p>
87312  * <p>Because this is a floating Component, its z-index will be managed by the global {@link Ext.WindowManager ZIndexManager}
87313  * object, and upon show, it will place itsef at the top of the hierarchy.</p>
87314  * <p>Example usage:</p>
87315  * <pre><code>
87316 // Basic mask:
87317 var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
87318 myMask.show();
87319 </code></pre>
87320
87321  */
87322
87323 Ext.define('Ext.LoadMask', {
87324
87325     extend: 'Ext.Component',
87326
87327     alias: 'widget.loadmask',
87328
87329     /* Begin Definitions */
87330
87331     mixins: {
87332         floating: 'Ext.util.Floating'
87333     },
87334
87335     uses: ['Ext.data.StoreManager'],
87336
87337     /* End Definitions */
87338
87339     /**
87340      * @cfg {Ext.data.Store} store
87341      * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
87342      * hidden on either load success, or load fail.
87343      */
87344
87345     /**
87346      * @cfg {String} msg
87347      * The text to display in a centered loading message box.
87348      */
87349     msg : 'Loading...',
87350     /**
87351      * @cfg {String} [msgCls="x-mask-loading"]
87352      * The CSS class to apply to the loading message element.
87353      */
87354     msgCls : Ext.baseCSSPrefix + 'mask-loading',
87355     
87356     /**
87357      * @cfg {Boolean} useMsg
87358      * Whether or not to use a loading message class or simply mask the bound element.
87359      */
87360     useMsg: true,
87361
87362     /**
87363      * Read-only. True if the mask is currently disabled so that it will not be displayed
87364      * @type Boolean
87365      */
87366     disabled: false,
87367
87368     baseCls: Ext.baseCSSPrefix + 'mask-msg',
87369
87370     renderTpl: '<div style="position:relative" class="{msgCls}"></div>',
87371
87372     // Private. The whole point is that there's a mask.
87373     modal: true,
87374
87375     // Private. Obviously, it's floating.
87376     floating: {
87377         shadow: 'frame'
87378     },
87379
87380     // Private. Masks are not focusable
87381     focusOnToFront: false,
87382
87383     /**
87384      * Creates new LoadMask.
87385      * @param {String/HTMLElement/Ext.Element} el The element, element ID, or DOM node you wish to mask.
87386      * <p>Also, may be a {@link Ext.Component Component} who's element you wish to mask. If a Component is specified, then
87387      * the mask will be automatically sized upon Component resize, the message box will be kept centered,
87388      * and the mask only be visible when the Component is.</p>
87389      * @param {Object} [config] The config object
87390      */
87391     constructor : function(el, config) {
87392         var me = this;
87393
87394         // If a Component passed, bind to it.
87395         if (el.isComponent) {
87396             me.ownerCt = el;
87397             me.bindComponent(el);
87398         }
87399         // Create a dumy Component encapsulating the specified Element
87400         else {
87401             me.ownerCt = new Ext.Component({
87402                 el: Ext.get(el),
87403                 rendered: true,
87404                 componentLayoutCounter: 1
87405             });
87406             me.container = el;
87407         }
87408         me.callParent([config]);
87409
87410         if (me.store) {
87411             me.bindStore(me.store, true);
87412         }
87413         me.renderData = {
87414             msgCls: me.msgCls
87415         };
87416         me.renderSelectors = {
87417             msgEl: 'div'
87418         };
87419     },
87420
87421     bindComponent: function(comp) {
87422         this.mon(comp, {
87423             resize: this.onComponentResize,
87424             scope: this
87425         });
87426     },
87427
87428     afterRender: function() {
87429         this.callParent(arguments);
87430         this.container = this.floatParent.getContentTarget();
87431     },
87432
87433     /**
87434      * @private
87435      * Called when this LoadMask's Component is resized. The toFront method rebases and resizes the modal mask.
87436      */
87437     onComponentResize: function() {
87438         var me = this;
87439         if (me.rendered && me.isVisible()) {
87440             me.toFront();
87441             me.center();
87442         }
87443     },
87444
87445     /**
87446      * Changes the data store bound to this LoadMask.
87447      * @param {Ext.data.Store} store The store to bind to this LoadMask
87448      */
87449     bindStore : function(store, initial) {
87450         var me = this;
87451
87452         if (!initial && me.store) {
87453             me.mun(me.store, {
87454                 scope: me,
87455                 beforeload: me.onBeforeLoad,
87456                 load: me.onLoad,
87457                 exception: me.onLoad
87458             });
87459             if (!store) {
87460                 me.store = null;
87461             }
87462         }
87463         if (store) {
87464             store = Ext.data.StoreManager.lookup(store);
87465             me.mon(store, {
87466                 scope: me,
87467                 beforeload: me.onBeforeLoad,
87468                 load: me.onLoad,
87469                 exception: me.onLoad
87470             });
87471
87472         }
87473         me.store = store;
87474         if (store && store.isLoading()) {
87475             me.onBeforeLoad();
87476         }
87477     },
87478
87479     onDisable : function() {
87480         this.callParent(arguments);
87481         if (this.loading) {
87482             this.onLoad();
87483         }
87484     },
87485
87486     // private
87487     onBeforeLoad : function() {
87488         var me = this,
87489             owner = me.ownerCt || me.floatParent,
87490             origin;
87491         if (!this.disabled) {
87492             // If the owning Component has not been layed out, defer so that the ZIndexManager
87493             // gets to read its layed out size when sizing the modal mask
87494             if (owner.componentLayoutCounter) {
87495                 Ext.Component.prototype.show.call(me);
87496             } else {
87497                 // The code below is a 'run-once' interceptor.
87498                 origin = owner.afterComponentLayout;
87499                 owner.afterComponentLayout = function() {
87500                     owner.afterComponentLayout = origin;
87501                     origin.apply(owner, arguments);
87502                     if(me.loading) {
87503                         Ext.Component.prototype.show.call(me);
87504                     }
87505                 };
87506             }
87507         }
87508     },
87509
87510     onHide: function(){
87511         var me = this;
87512         me.callParent(arguments);
87513         me.showOnParentShow = true;
87514     },
87515
87516     onShow: function() {
87517         var me = this,
87518             msgEl = me.msgEl;
87519             
87520         me.callParent(arguments);
87521         me.loading = true;
87522         if (me.useMsg) {
87523             msgEl.show().update(me.msg);
87524         } else {
87525             msgEl.parent().hide();
87526         }
87527     },
87528
87529     afterShow: function() {
87530         this.callParent(arguments);
87531         this.center();
87532     },
87533
87534     // private
87535     onLoad : function() {
87536         this.loading = false;
87537         Ext.Component.prototype.hide.call(this);
87538     }
87539 });
87540 /**
87541  * @class Ext.view.AbstractView
87542  * @extends Ext.Component
87543  * This is an abstract superclass and should not be used directly. Please see {@link Ext.view.View}.
87544  * @private
87545  */
87546 Ext.define('Ext.view.AbstractView', {
87547     extend: 'Ext.Component',
87548     alternateClassName: 'Ext.view.AbstractView',
87549     requires: [
87550         'Ext.LoadMask',
87551         'Ext.data.StoreManager',
87552         'Ext.CompositeElementLite',
87553         'Ext.DomQuery',
87554         'Ext.selection.DataViewModel'
87555     ],
87556
87557     inheritableStatics: {
87558         getRecord: function(node) {
87559             return this.getBoundView(node).getRecord(node);
87560         },
87561
87562         getBoundView: function(node) {
87563             return Ext.getCmp(node.boundView);
87564         }
87565     },
87566
87567     /**
87568      * @cfg {String/String[]/Ext.XTemplate} tpl (required)
87569      * The HTML fragment or an array of fragments that will make up the template used by this DataView.  This should
87570      * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
87571      */
87572     /**
87573      * @cfg {Ext.data.Store} store (required)
87574      * The {@link Ext.data.Store} to bind this DataView to.
87575      */
87576
87577     /**
87578      * @cfg {Boolean} deferInitialRefresh
87579      * <p>Defaults to <code>true</code> to defer the initial refresh of the view.</p>
87580      * <p>This allows the View to execute its render and initial layout more quickly because the process will not be encumbered
87581      * by the expensive update of the view structure.</p>
87582      * <p><b>Important: </b>Be aware that this will mean that the View's item elements will not be available immediately upon render, so
87583      * <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.
87584      * Or set <code>deferInitialrefresh</code> to false, but this will be at the cost of slower rendering.</p>
87585      */
87586     deferInitialRefresh: true,
87587
87588     /**
87589      * @cfg {String} itemSelector (required)
87590      * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
87591      * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
87592      * working with. The itemSelector is used to map DOM nodes to records. As such, there should
87593      * only be one root level element that matches the selector for each record.
87594      */
87595
87596     /**
87597      * @cfg {String} itemCls
87598      * Specifies the class to be assigned to each element in the view when used in conjunction with the
87599      * {@link #itemTpl} configuration.
87600      */
87601     itemCls: Ext.baseCSSPrefix + 'dataview-item',
87602
87603     /**
87604      * @cfg {String/String[]/Ext.XTemplate} itemTpl
87605      * The inner portion of the item template to be rendered. Follows an XTemplate
87606      * structure and will be placed inside of a tpl.
87607      */
87608
87609     /**
87610      * @cfg {String} overItemCls
87611      * A CSS class to apply to each item in the view on mouseover.
87612      * Ensure {@link #trackOver} is set to `true` to make use of this.
87613      */
87614
87615     /**
87616      * @cfg {String} loadingText
87617      * A string to display during data load operations.  If specified, this text will be
87618      * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
87619      * contents will continue to display normally until the new data is loaded and the contents are replaced.
87620      */
87621     loadingText: 'Loading...',
87622
87623     /**
87624      * @cfg {Boolean/Object} loadMask
87625      * False to disable a load mask from displaying will the view is loading. This can also be a
87626      * {@link Ext.LoadMask} configuration object.
87627      */
87628     loadMask: true,
87629
87630     /**
87631      * @cfg {String} loadingCls
87632      * The CSS class to apply to the loading message element. Defaults to Ext.LoadMask.prototype.msgCls "x-mask-loading".
87633      */
87634
87635     /**
87636      * @cfg {Boolean} loadingUseMsg
87637      * Whether or not to use the loading message.
87638      * @private
87639      */
87640     loadingUseMsg: true,
87641
87642
87643     /**
87644      * @cfg {Number} loadingHeight
87645      * If specified, gives an explicit height for the data view when it is showing the {@link #loadingText},
87646      * if that is specified. This is useful to prevent the view's height from collapsing to zero when the
87647      * loading mask is applied and there are no other contents in the data view.
87648      */
87649
87650     /**
87651      * @cfg {String} [selectedItemCls='x-view-selected']
87652      * A CSS class to apply to each selected item in the view.
87653      */
87654     selectedItemCls: Ext.baseCSSPrefix + 'item-selected',
87655
87656     /**
87657      * @cfg {String} emptyText
87658      * The text to display in the view when there is no data to display.
87659      * Note that when using local data the emptyText will not be displayed unless you set
87660      * the {@link #deferEmptyText} option to false.
87661      */
87662     emptyText: "",
87663
87664     /**
87665      * @cfg {Boolean} deferEmptyText
87666      * True to defer emptyText being applied until the store's first load.
87667      */
87668     deferEmptyText: true,
87669
87670     /**
87671      * @cfg {Boolean} trackOver
87672      * True to enable mouseenter and mouseleave events
87673      */
87674     trackOver: false,
87675
87676     /**
87677      * @cfg {Boolean} blockRefresh
87678      * Set this to true to ignore datachanged events on the bound store. This is useful if
87679      * you wish to provide custom transition animations via a plugin
87680      */
87681     blockRefresh: false,
87682
87683     /**
87684      * @cfg {Boolean} disableSelection
87685      * True to disable selection within the DataView. This configuration will lock the selection model
87686      * that the DataView uses.
87687      */
87688
87689
87690     //private
87691     last: false,
87692
87693     triggerEvent: 'itemclick',
87694     triggerCtEvent: 'containerclick',
87695
87696     addCmpEvents: function() {
87697
87698     },
87699
87700     // private
87701     initComponent : function(){
87702         var me = this,
87703             isDef = Ext.isDefined,
87704             itemTpl = me.itemTpl,
87705             memberFn = {};
87706
87707         if (itemTpl) {
87708             if (Ext.isArray(itemTpl)) {
87709                 // string array
87710                 itemTpl = itemTpl.join('');
87711             } else if (Ext.isObject(itemTpl)) {
87712                 // tpl instance
87713                 memberFn = Ext.apply(memberFn, itemTpl.initialConfig);
87714                 itemTpl = itemTpl.html;
87715             }
87716
87717             if (!me.itemSelector) {
87718                 me.itemSelector = '.' + me.itemCls;
87719             }
87720
87721             itemTpl = Ext.String.format('<tpl for="."><div class="{0}">{1}</div></tpl>', me.itemCls, itemTpl);
87722             me.tpl = Ext.create('Ext.XTemplate', itemTpl, memberFn);
87723         }
87724
87725         if (!isDef(me.tpl) || !isDef(me.itemSelector)) {
87726             Ext.Error.raise({
87727                 sourceClass: 'Ext.view.View',
87728                 tpl: me.tpl,
87729                 itemSelector: me.itemSelector,
87730                 msg: "DataView requires both tpl and itemSelector configurations to be defined."
87731             });
87732         }
87733
87734         me.callParent();
87735         if(Ext.isString(me.tpl) || Ext.isArray(me.tpl)){
87736             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
87737         }
87738
87739         // backwards compat alias for overClass/selectedClass
87740         // TODO: Consider support for overCls generation Ext.Component config
87741         if (isDef(me.overCls) || isDef(me.overClass)) {
87742             if (Ext.isDefined(Ext.global.console)) {
87743                 Ext.global.console.warn('Ext.view.View: Using the deprecated overCls or overClass configuration. Use overItemCls instead.');
87744             }
87745             me.overItemCls = me.overCls || me.overClass;
87746             delete me.overCls;
87747             delete me.overClass;
87748         }
87749
87750         if (me.overItemCls) {
87751             me.trackOver = true;
87752         }
87753
87754         if (isDef(me.selectedCls) || isDef(me.selectedClass)) {
87755             if (Ext.isDefined(Ext.global.console)) {
87756                 Ext.global.console.warn('Ext.view.View: Using the deprecated selectedCls or selectedClass configuration. Use selectedItemCls instead.');
87757             }
87758             me.selectedItemCls = me.selectedCls || me.selectedClass;
87759             delete me.selectedCls;
87760             delete me.selectedClass;
87761         }
87762
87763         me.addEvents(
87764             /**
87765              * @event beforerefresh
87766              * Fires before the view is refreshed
87767              * @param {Ext.view.View} this The DataView object
87768              */
87769             'beforerefresh',
87770             /**
87771              * @event refresh
87772              * Fires when the view is refreshed
87773              * @param {Ext.view.View} this The DataView object
87774              */
87775             'refresh',
87776             /**
87777              * @event viewready
87778              * Fires when the View's item elements representing Store items has been rendered. If the {@link #deferInitialRefresh} flag
87779              * was set (and it is <code>true</code> by default), this will be <b>after</b> initial render, and no items will be available
87780              * for selection until this event fires.
87781              * @param {Ext.view.View} this
87782              */
87783             'viewready',
87784             /**
87785              * @event itemupdate
87786              * Fires when the node associated with an individual record is updated
87787              * @param {Ext.data.Model} record The model instance
87788              * @param {Number} index The index of the record/node
87789              * @param {HTMLElement} node The node that has just been updated
87790              */
87791             'itemupdate',
87792             /**
87793              * @event itemadd
87794              * Fires when the nodes associated with an recordset have been added to the underlying store
87795              * @param {Ext.data.Model[]} records The model instance
87796              * @param {Number} index The index at which the set of record/nodes starts
87797              * @param {HTMLElement[]} node The node that has just been updated
87798              */
87799             'itemadd',
87800             /**
87801              * @event itemremove
87802              * Fires when the node associated with an individual record is removed
87803              * @param {Ext.data.Model} record The model instance
87804              * @param {Number} index The index of the record/node
87805              */
87806             'itemremove'
87807         );
87808
87809         me.addCmpEvents();
87810
87811         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
87812         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
87813         me.all = new Ext.CompositeElementLite();
87814     },
87815
87816     onRender: function() {
87817         var me = this,
87818             mask = me.loadMask,
87819             cfg = {
87820                 msg: me.loadingText,
87821                 msgCls: me.loadingCls,
87822                 useMsg: me.loadingUseMsg
87823             };
87824
87825         me.callParent(arguments);
87826
87827         if (mask) {
87828             // either a config object
87829             if (Ext.isObject(mask)) {
87830                 cfg = Ext.apply(cfg, mask);
87831             }
87832             // Attach the LoadMask to a *Component* so that it can be sensitive to resizing during long loads.
87833             // If this DataView is floating, then mask this DataView.
87834             // Otherwise, mask its owning Container (or this, if there *is* no owning Container).
87835             // LoadMask captures the element upon render.
87836             me.loadMask = Ext.create('Ext.LoadMask', me, cfg);
87837             me.loadMask.on({
87838                 scope: me,
87839                 beforeshow: me.onMaskBeforeShow,
87840                 hide: me.onMaskHide
87841             });
87842         }
87843     },
87844
87845     onMaskBeforeShow: function(){
87846         var loadingHeight = this.loadingHeight;
87847         
87848         this.getSelectionModel().deselectAll();
87849         if (loadingHeight) {
87850             this.setCalculatedSize(undefined, loadingHeight);
87851         }
87852     },
87853
87854     onMaskHide: function(){
87855         var me = this;
87856         
87857         if (!me.destroying && me.loadingHeight) {
87858             me.setHeight(me.height);
87859         }
87860     },
87861
87862     afterRender: function() {
87863         this.callParent(arguments);
87864
87865         // Init the SelectionModel after any on('render') listeners have been added.
87866         // Drag plugins create a DragDrop instance in a render listener, and that needs
87867         // to see an itemmousedown event first.
87868         this.getSelectionModel().bindComponent(this);
87869     },
87870
87871     /**
87872      * Gets the selection model for this view.
87873      * @return {Ext.selection.Model} The selection model
87874      */
87875     getSelectionModel: function(){
87876         var me = this,
87877             mode = 'SINGLE';
87878
87879         if (!me.selModel) {
87880             me.selModel = {};
87881         }
87882
87883         if (me.simpleSelect) {
87884             mode = 'SIMPLE';
87885         } else if (me.multiSelect) {
87886             mode = 'MULTI';
87887         }
87888
87889         Ext.applyIf(me.selModel, {
87890             allowDeselect: me.allowDeselect,
87891             mode: mode
87892         });
87893
87894         if (!me.selModel.events) {
87895             me.selModel = Ext.create('Ext.selection.DataViewModel', me.selModel);
87896         }
87897
87898         if (!me.selModel.hasRelaySetup) {
87899             me.relayEvents(me.selModel, [
87900                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
87901             ]);
87902             me.selModel.hasRelaySetup = true;
87903         }
87904
87905         // lock the selection model if user
87906         // has disabled selection
87907         if (me.disableSelection) {
87908             me.selModel.locked = true;
87909         }
87910
87911         return me.selModel;
87912     },
87913
87914     /**
87915      * Refreshes the view by reloading the data from the store and re-rendering the template.
87916      */
87917     refresh: function() {
87918         var me = this,
87919             el,
87920             records;
87921
87922         if (!me.rendered || me.isDestroyed) {
87923             return;
87924         }
87925
87926         me.fireEvent('beforerefresh', me);
87927         el = me.getTargetEl();
87928         records = me.store.getRange();
87929
87930         el.update('');
87931         if (records.length < 1) {
87932             if (!me.deferEmptyText || me.hasSkippedEmptyText) {
87933                 el.update(me.emptyText);
87934             }
87935             me.all.clear();
87936         } else {
87937             me.tpl.overwrite(el, me.collectData(records, 0));
87938             me.all.fill(Ext.query(me.getItemSelector(), el.dom));
87939             me.updateIndexes(0);
87940         }
87941
87942         me.selModel.refresh();
87943         me.hasSkippedEmptyText = true;
87944         me.fireEvent('refresh', me);
87945
87946         // Upon first refresh, fire the viewready event.
87947         // Reconfiguring the grid "renews" this event.
87948         if (!me.viewReady) {
87949             // Fire an event when deferred content becomes available.
87950             // This supports grid Panel's deferRowRender capability
87951             me.viewReady = true;
87952             me.fireEvent('viewready', me);
87953         }
87954     },
87955
87956     /**
87957      * Function which can be overridden to provide custom formatting for each Record that is used by this
87958      * DataView's {@link #tpl template} to render each node.
87959      * @param {Object/Object[]} data The raw data object that was used to create the Record.
87960      * @param {Number} recordIndex the index number of the Record being prepared for rendering.
87961      * @param {Ext.data.Model} record The Record being prepared for rendering.
87962      * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
87963      * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
87964      */
87965     prepareData: function(data, index, record) {
87966         if (record) {
87967             Ext.apply(data, record.getAssociatedData());
87968         }
87969         return data;
87970     },
87971
87972     /**
87973      * <p>Function which can be overridden which returns the data object passed to this
87974      * DataView's {@link #tpl template} to render the whole DataView.</p>
87975      * <p>This is usually an Array of data objects, each element of which is processed by an
87976      * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
87977      * data object as an Array. However, <i>named</i> properties may be placed into the data object to
87978      * provide non-repeating data such as headings, totals etc.</p>
87979      * @param {Ext.data.Model[]} records An Array of {@link Ext.data.Model}s to be rendered into the DataView.
87980      * @param {Number} startIndex the index number of the Record being prepared for rendering.
87981      * @return {Object[]} An Array of data objects to be processed by a repeating XTemplate. May also
87982      * contain <i>named</i> properties.
87983      */
87984     collectData : function(records, startIndex){
87985         var r = [],
87986             i = 0,
87987             len = records.length,
87988             record;
87989
87990         for(; i < len; i++){
87991             record = records[i];
87992             r[r.length] = this.prepareData(record[record.persistenceProperty], startIndex + i, record);
87993         }
87994         return r;
87995     },
87996
87997     // private
87998     bufferRender : function(records, index){
87999         var div = document.createElement('div');
88000         this.tpl.overwrite(div, this.collectData(records, index));
88001         return Ext.query(this.getItemSelector(), div);
88002     },
88003
88004     // private
88005     onUpdate : function(ds, record){
88006         var me = this,
88007             index = me.store.indexOf(record),
88008             node;
88009
88010         if (index > -1){
88011             node = me.bufferRender([record], index)[0];
88012             // ensure the node actually exists in the DOM
88013             if (me.getNode(record)) {
88014                 me.all.replaceElement(index, node, true);
88015                 me.updateIndexes(index, index);
88016                 // Maintain selection after update
88017                 // TODO: Move to approriate event handler.
88018                 me.selModel.refresh();
88019                 me.fireEvent('itemupdate', record, index, node);
88020             }
88021         }
88022
88023     },
88024
88025     // private
88026     onAdd : function(ds, records, index) {
88027         var me = this,
88028             nodes;
88029
88030         if (me.all.getCount() === 0) {
88031             me.refresh();
88032             return;
88033         }
88034
88035         nodes = me.bufferRender(records, index);
88036         me.doAdd(nodes, records, index);
88037
88038         me.selModel.refresh();
88039         me.updateIndexes(index);
88040         me.fireEvent('itemadd', records, index, nodes);
88041     },
88042
88043     doAdd: function(nodes, records, index) {
88044         var all = this.all;
88045
88046         if (index < all.getCount()) {
88047             all.item(index).insertSibling(nodes, 'before', true);
88048         } else {
88049             all.last().insertSibling(nodes, 'after', true);
88050         }
88051
88052         Ext.Array.insert(all.elements, index, nodes);
88053     },
88054
88055     // private
88056     onRemove : function(ds, record, index) {
88057         var me = this;
88058
88059         me.doRemove(record, index);
88060         me.updateIndexes(index);
88061         if (me.store.getCount() === 0){
88062             me.refresh();
88063         }
88064         me.fireEvent('itemremove', record, index);
88065     },
88066
88067     doRemove: function(record, index) {
88068         this.all.removeElement(index, true);
88069     },
88070
88071     /**
88072      * Refreshes an individual node's data from the store.
88073      * @param {Number} index The item's data index in the store
88074      */
88075     refreshNode : function(index){
88076         this.onUpdate(this.store, this.store.getAt(index));
88077     },
88078
88079     // private
88080     updateIndexes : function(startIndex, endIndex) {
88081         var ns = this.all.elements,
88082             records = this.store.getRange(),
88083             i;
88084             
88085         startIndex = startIndex || 0;
88086         endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
88087         for(i = startIndex; i <= endIndex; i++){
88088             ns[i].viewIndex = i;
88089             ns[i].viewRecordId = records[i].internalId;
88090             if (!ns[i].boundView) {
88091                 ns[i].boundView = this.id;
88092             }
88093         }
88094     },
88095
88096     /**
88097      * Returns the store associated with this DataView.
88098      * @return {Ext.data.Store} The store
88099      */
88100     getStore : function(){
88101         return this.store;
88102     },
88103
88104     /**
88105      * Changes the data store bound to this view and refreshes it.
88106      * @param {Ext.data.Store} store The store to bind to this view
88107      */
88108     bindStore : function(store, initial) {
88109         var me = this,
88110             maskStore;
88111
88112         if (!initial && me.store) {
88113             if (store !== me.store && me.store.autoDestroy) {
88114                 me.store.destroyStore();
88115             }
88116             else {
88117                 me.mun(me.store, {
88118                     scope: me,
88119                     datachanged: me.onDataChanged,
88120                     add: me.onAdd,
88121                     remove: me.onRemove,
88122                     update: me.onUpdate,
88123                     clear: me.refresh
88124                 });
88125             }
88126             if (!store) {
88127                 // Ensure we have an instantiated LoadMask before we unbind it.
88128                 if (me.loadMask && me.loadMask.bindStore) {
88129                     me.loadMask.bindStore(null);
88130                 }
88131                 me.store = null;
88132             }
88133         }
88134         if (store) {
88135             store = Ext.data.StoreManager.lookup(store);
88136             me.mon(store, {
88137                 scope: me,
88138                 datachanged: me.onDataChanged,
88139                 add: me.onAdd,
88140                 remove: me.onRemove,
88141                 update: me.onUpdate,
88142                 clear: me.refresh
88143             });
88144             // Ensure we have an instantiated LoadMask before we bind it.
88145             if (me.loadMask && me.loadMask.bindStore) {
88146                 // View's store is a NodeStore, use owning TreePanel's Store
88147                 if (Ext.Array.contains(store.alias, 'store.node')) {
88148                     maskStore = this.ownerCt.store;
88149                 } else {
88150                     maskStore = store;
88151                 }
88152                 me.loadMask.bindStore(maskStore);
88153             }
88154         }
88155
88156         // Flag to say that initial refresh has not been performed.
88157         // Set here rather than at initialization time, so that a reconfigure with a new store will refire viewready
88158         me.viewReady = false;
88159
88160         me.store = store;
88161         // Bind the store to our selection model
88162         me.getSelectionModel().bind(store);
88163
88164         /*
88165          * This code used to have checks for:
88166          * if (store && (!initial || store.getCount() || me.emptyText)) {
88167          * Instead, just trigger a refresh and let the view itself figure out
88168          * what needs to happen. It can cause incorrect display if our store
88169          * has no data.
88170          */
88171         if (store) {
88172             if (initial && me.deferInitialRefresh) {
88173                 Ext.Function.defer(function () {
88174                     if (!me.isDestroyed) {
88175                         me.refresh(true);
88176                     }
88177                 }, 1);
88178             } else {
88179                 me.refresh(true);
88180             }
88181         }
88182     },
88183
88184     /**
88185      * @private
88186      * Calls this.refresh if this.blockRefresh is not true
88187      */
88188     onDataChanged: function() {
88189         if (this.blockRefresh !== true) {
88190             this.refresh.apply(this, arguments);
88191         }
88192     },
88193
88194     /**
88195      * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
88196      * @param {HTMLElement} node
88197      * @return {HTMLElement} The template node
88198      */
88199     findItemByChild: function(node){
88200         return Ext.fly(node).findParent(this.getItemSelector(), this.getTargetEl());
88201     },
88202
88203     /**
88204      * Returns the template node by the Ext.EventObject or null if it is not found.
88205      * @param {Ext.EventObject} e
88206      */
88207     findTargetByEvent: function(e) {
88208         return e.getTarget(this.getItemSelector(), this.getTargetEl());
88209     },
88210
88211
88212     /**
88213      * Gets the currently selected nodes.
88214      * @return {HTMLElement[]} An array of HTMLElements
88215      */
88216     getSelectedNodes: function(){
88217         var nodes   = [],
88218             records = this.selModel.getSelection(),
88219             ln = records.length,
88220             i  = 0;
88221
88222         for (; i < ln; i++) {
88223             nodes.push(this.getNode(records[i]));
88224         }
88225
88226         return nodes;
88227     },
88228
88229     /**
88230      * Gets an array of the records from an array of nodes
88231      * @param {HTMLElement[]} nodes The nodes to evaluate
88232      * @return {Ext.data.Model[]} records The {@link Ext.data.Model} objects
88233      */
88234     getRecords: function(nodes) {
88235         var records = [],
88236             i = 0,
88237             len = nodes.length,
88238             data = this.store.data;
88239
88240         for (; i < len; i++) {
88241             records[records.length] = data.getByKey(nodes[i].viewRecordId);
88242         }
88243
88244         return records;
88245     },
88246
88247     /**
88248      * Gets a record from a node
88249      * @param {Ext.Element/HTMLElement} node The node to evaluate
88250      *
88251      * @return {Ext.data.Model} record The {@link Ext.data.Model} object
88252      */
88253     getRecord: function(node){
88254         return this.store.data.getByKey(Ext.getDom(node).viewRecordId);
88255     },
88256
88257
88258     /**
88259      * Returns true if the passed node is selected, else false.
88260      * @param {HTMLElement/Number/Ext.data.Model} node The node, node index or record to check
88261      * @return {Boolean} True if selected, else false
88262      */
88263     isSelected : function(node) {
88264         // TODO: El/Idx/Record
88265         var r = this.getRecord(node);
88266         return this.selModel.isSelected(r);
88267     },
88268
88269     /**
88270      * Selects a record instance by record instance or index.
88271      * @param {Ext.data.Model[]/Number} records An array of records or an index
88272      * @param {Boolean} [keepExisting] True to keep existing selections
88273      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
88274      */
88275     select: function(records, keepExisting, suppressEvent) {
88276         this.selModel.select(records, keepExisting, suppressEvent);
88277     },
88278
88279     /**
88280      * Deselects a record instance by record instance or index.
88281      * @param {Ext.data.Model[]/Number} records An array of records or an index
88282      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
88283      */
88284     deselect: function(records, suppressEvent) {
88285         this.selModel.deselect(records, suppressEvent);
88286     },
88287
88288     /**
88289      * Gets a template node.
88290      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node,
88291      * the id of a template node or the record associated with the node.
88292      * @return {HTMLElement} The node or null if it wasn't found
88293      */
88294     getNode : function(nodeInfo) {
88295         if (!this.rendered) {
88296             return null;
88297         }
88298         if (Ext.isString(nodeInfo)) {
88299             return document.getElementById(nodeInfo);
88300         }
88301         if (Ext.isNumber(nodeInfo)) {
88302             return this.all.elements[nodeInfo];
88303         }
88304         if (nodeInfo instanceof Ext.data.Model) {
88305             return this.getNodeByRecord(nodeInfo);
88306         }
88307         return nodeInfo; // already an HTMLElement
88308     },
88309
88310     /**
88311      * @private
88312      */
88313     getNodeByRecord: function(record) {
88314         var ns = this.all.elements,
88315             ln = ns.length,
88316             i = 0;
88317
88318         for (; i < ln; i++) {
88319             if (ns[i].viewRecordId === record.internalId) {
88320                 return ns[i];
88321             }
88322         }
88323
88324         return null;
88325     },
88326
88327     /**
88328      * Gets a range nodes.
88329      * @param {Number} start (optional) The index of the first node in the range
88330      * @param {Number} end (optional) The index of the last node in the range
88331      * @return {HTMLElement[]} An array of nodes
88332      */
88333     getNodes: function(start, end) {
88334         var ns = this.all.elements,
88335             nodes = [],
88336             i;
88337
88338         start = start || 0;
88339         end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
88340         if (start <= end) {
88341             for (i = start; i <= end && ns[i]; i++) {
88342                 nodes.push(ns[i]);
88343             }
88344         } else {
88345             for (i = start; i >= end && ns[i]; i--) {
88346                 nodes.push(ns[i]);
88347             }
88348         }
88349         return nodes;
88350     },
88351
88352     /**
88353      * Finds the index of the passed node.
88354      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node, the id of a template node
88355      * or a record associated with a node.
88356      * @return {Number} The index of the node or -1
88357      */
88358     indexOf: function(node) {
88359         node = this.getNode(node);
88360         if (Ext.isNumber(node.viewIndex)) {
88361             return node.viewIndex;
88362         }
88363         return this.all.indexOf(node);
88364     },
88365
88366     onDestroy : function() {
88367         var me = this;
88368
88369         me.all.clear();
88370         me.callParent();
88371         me.bindStore(null);
88372         me.selModel.destroy();
88373     },
88374
88375     // invoked by the selection model to maintain visual UI cues
88376     onItemSelect: function(record) {
88377         var node = this.getNode(record);
88378         
88379         if (node) {
88380             Ext.fly(node).addCls(this.selectedItemCls);
88381         }
88382     },
88383
88384     // invoked by the selection model to maintain visual UI cues
88385     onItemDeselect: function(record) {
88386         var node = this.getNode(record);
88387         
88388         if (node) {
88389             Ext.fly(node).removeCls(this.selectedItemCls);
88390         }
88391     },
88392
88393     getItemSelector: function() {
88394         return this.itemSelector;
88395     }
88396 }, function() {
88397     // all of this information is available directly
88398     // from the SelectionModel itself, the only added methods
88399     // to DataView regarding selection will perform some transformation/lookup
88400     // between HTMLElement/Nodes to records and vice versa.
88401     Ext.deprecate('extjs', '4.0', function() {
88402         Ext.view.AbstractView.override({
88403             /**
88404              * @cfg {Boolean} [multiSelect=false]
88405              * True to allow selection of more than one item at a time, false to allow selection of only a single item
88406              * at a time or no selection at all, depending on the value of {@link #singleSelect}.
88407              */
88408             /**
88409              * @cfg {Boolean} [singleSelect=false]
88410              * True to allow selection of exactly one item at a time, false to allow no selection at all.
88411              * Note that if {@link #multiSelect} = true, this value will be ignored.
88412              */
88413             /**
88414              * @cfg {Boolean} [simpleSelect=false]
88415              * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
88416              * false to force the user to hold Ctrl or Shift to select more than on item.
88417              */
88418
88419             /**
88420              * Gets the number of selected nodes.
88421              * @return {Number} The node count
88422              */
88423             getSelectionCount : function(){
88424                 if (Ext.global.console) {
88425                     Ext.global.console.warn("DataView: getSelectionCount will be removed, please interact with the Ext.selection.DataViewModel");
88426                 }
88427                 return this.selModel.getSelection().length;
88428             },
88429
88430             /**
88431              * Gets an array of the selected records
88432              * @return {Ext.data.Model[]} An array of {@link Ext.data.Model} objects
88433              */
88434             getSelectedRecords : function(){
88435                 if (Ext.global.console) {
88436                     Ext.global.console.warn("DataView: getSelectedRecords will be removed, please interact with the Ext.selection.DataViewModel");
88437                 }
88438                 return this.selModel.getSelection();
88439             },
88440
88441             select: function(records, keepExisting, supressEvents) {
88442                 if (Ext.global.console) {
88443                     Ext.global.console.warn("DataView: select will be removed, please access select through a DataView's SelectionModel, ie: view.getSelectionModel().select()");
88444                 }
88445                 var sm = this.getSelectionModel();
88446                 return sm.select.apply(sm, arguments);
88447             },
88448
88449             clearSelections: function() {
88450                 if (Ext.global.console) {
88451                     Ext.global.console.warn("DataView: clearSelections will be removed, please access deselectAll through DataView's SelectionModel, ie: view.getSelectionModel().deselectAll()");
88452                 }
88453                 var sm = this.getSelectionModel();
88454                 return sm.deselectAll();
88455             }
88456         });
88457     });
88458 });
88459
88460 /**
88461  * @class Ext.Action
88462  * <p>An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
88463  * can be usefully shared among multiple components.  Actions let you share handlers, configuration options and UI
88464  * updates across any components that support the Action interface (primarily {@link Ext.toolbar.Toolbar}, {@link Ext.button.Button}
88465  * and {@link Ext.menu.Menu} components).</p>
88466  * <p>Use a single Action instance as the config object for any number of UI Components which share the same configuration. The
88467  * Action not only supplies the configuration, but allows all Components based upon it to have a common set of methods
88468  * called at once through a single call to the Action.</p>
88469  * <p>Any Component that is to be configured with an Action must also support
88470  * the following methods:<ul>
88471  * <li><code>setText(string)</code></li>
88472  * <li><code>setIconCls(string)</code></li>
88473  * <li><code>setDisabled(boolean)</code></li>
88474  * <li><code>setVisible(boolean)</code></li>
88475  * <li><code>setHandler(function)</code></li></ul></p>
88476  * <p>This allows the Action to control its associated Components.</p>
88477  * Example usage:<br>
88478  * <pre><code>
88479 // Define the shared Action.  Each Component below will have the same
88480 // display text and icon, and will display the same message on click.
88481 var action = new Ext.Action({
88482     {@link #text}: 'Do something',
88483     {@link #handler}: function(){
88484         Ext.Msg.alert('Click', 'You did something.');
88485     },
88486     {@link #iconCls}: 'do-something',
88487     {@link #itemId}: 'myAction'
88488 });
88489
88490 var panel = new Ext.panel.Panel({
88491     title: 'Actions',
88492     width: 500,
88493     height: 300,
88494     tbar: [
88495         // Add the Action directly to a toolbar as a menu button
88496         action,
88497         {
88498             text: 'Action Menu',
88499             // Add the Action to a menu as a text item
88500             menu: [action]
88501         }
88502     ],
88503     items: [
88504         // Add the Action to the panel body as a standard button
88505         new Ext.button.Button(action)
88506     ],
88507     renderTo: Ext.getBody()
88508 });
88509
88510 // Change the text for all components using the Action
88511 action.setText('Something else');
88512
88513 // Reference an Action through a container using the itemId
88514 var btn = panel.getComponent('myAction');
88515 var aRef = btn.baseAction;
88516 aRef.setText('New text');
88517 </code></pre>
88518  */
88519 Ext.define('Ext.Action', {
88520
88521     /* Begin Definitions */
88522
88523     /* End Definitions */
88524
88525     /**
88526      * @cfg {String} [text='']
88527      * The text to set for all components configured by this Action.
88528      */
88529     /**
88530      * @cfg {String} [iconCls='']
88531      * The CSS class selector that specifies a background image to be used as the header icon for
88532      * all components configured by this Action.
88533      * <p>An example of specifying a custom icon class would be something like:
88534      * </p><pre><code>
88535 // specify the property in the config for the class:
88536      ...
88537      iconCls: 'do-something'
88538
88539 // css class that specifies background image to be used as the icon image:
88540 .do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
88541 </code></pre>
88542      */
88543     /**
88544      * @cfg {Boolean} [disabled=false]
88545      * True to disable all components configured by this Action, false to enable them.
88546      */
88547     /**
88548      * @cfg {Boolean} [hidden=false]
88549      * True to hide all components configured by this Action, false to show them.
88550      */
88551     /**
88552      * @cfg {Function} handler
88553      * The function that will be invoked by each component tied to this Action
88554      * when the component's primary event is triggered.
88555      */
88556     /**
88557      * @cfg {String} itemId
88558      * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
88559      */
88560     /**
88561      * @cfg {Object} scope
88562      * The scope (this reference) in which the {@link #handler} is executed.
88563      * Defaults to the browser window.
88564      */
88565
88566     /**
88567      * Creates new Action.
88568      * @param {Object} config Config object.
88569      */
88570     constructor : function(config){
88571         this.initialConfig = config;
88572         this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
88573         this.items = [];
88574     },
88575
88576     // private
88577     isAction : true,
88578
88579     /**
88580      * Sets the text to be displayed by all components configured by this Action.
88581      * @param {String} text The text to display
88582      */
88583     setText : function(text){
88584         this.initialConfig.text = text;
88585         this.callEach('setText', [text]);
88586     },
88587
88588     /**
88589      * Gets the text currently displayed by all components configured by this Action.
88590      */
88591     getText : function(){
88592         return this.initialConfig.text;
88593     },
88594
88595     /**
88596      * Sets the icon CSS class for all components configured by this Action.  The class should supply
88597      * a background image that will be used as the icon image.
88598      * @param {String} cls The CSS class supplying the icon image
88599      */
88600     setIconCls : function(cls){
88601         this.initialConfig.iconCls = cls;
88602         this.callEach('setIconCls', [cls]);
88603     },
88604
88605     /**
88606      * Gets the icon CSS class currently used by all components configured by this Action.
88607      */
88608     getIconCls : function(){
88609         return this.initialConfig.iconCls;
88610     },
88611
88612     /**
88613      * Sets the disabled state of all components configured by this Action.  Shortcut method
88614      * for {@link #enable} and {@link #disable}.
88615      * @param {Boolean} disabled True to disable the component, false to enable it
88616      */
88617     setDisabled : function(v){
88618         this.initialConfig.disabled = v;
88619         this.callEach('setDisabled', [v]);
88620     },
88621
88622     /**
88623      * Enables all components configured by this Action.
88624      */
88625     enable : function(){
88626         this.setDisabled(false);
88627     },
88628
88629     /**
88630      * Disables all components configured by this Action.
88631      */
88632     disable : function(){
88633         this.setDisabled(true);
88634     },
88635
88636     /**
88637      * Returns true if the components using this Action are currently disabled, else returns false.
88638      */
88639     isDisabled : function(){
88640         return this.initialConfig.disabled;
88641     },
88642
88643     /**
88644      * Sets the hidden state of all components configured by this Action.  Shortcut method
88645      * for <code>{@link #hide}</code> and <code>{@link #show}</code>.
88646      * @param {Boolean} hidden True to hide the component, false to show it
88647      */
88648     setHidden : function(v){
88649         this.initialConfig.hidden = v;
88650         this.callEach('setVisible', [!v]);
88651     },
88652
88653     /**
88654      * Shows all components configured by this Action.
88655      */
88656     show : function(){
88657         this.setHidden(false);
88658     },
88659
88660     /**
88661      * Hides all components configured by this Action.
88662      */
88663     hide : function(){
88664         this.setHidden(true);
88665     },
88666
88667     /**
88668      * Returns true if the components configured by this Action are currently hidden, else returns false.
88669      */
88670     isHidden : function(){
88671         return this.initialConfig.hidden;
88672     },
88673
88674     /**
88675      * Sets the function that will be called by each Component using this action when its primary event is triggered.
88676      * @param {Function} fn The function that will be invoked by the action's components.  The function
88677      * will be called with no arguments.
88678      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component firing the event.
88679      */
88680     setHandler : function(fn, scope){
88681         this.initialConfig.handler = fn;
88682         this.initialConfig.scope = scope;
88683         this.callEach('setHandler', [fn, scope]);
88684     },
88685
88686     /**
88687      * Executes the specified function once for each Component currently tied to this Action.  The function passed
88688      * in should accept a single argument that will be an object that supports the basic Action config/method interface.
88689      * @param {Function} fn The function to execute for each component
88690      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed.  Defaults to the Component.
88691      */
88692     each : function(fn, scope){
88693         Ext.each(this.items, fn, scope);
88694     },
88695
88696     // private
88697     callEach : function(fnName, args){
88698         var items = this.items,
88699             i = 0,
88700             len = items.length;
88701
88702         for(; i < len; i++){
88703             items[i][fnName].apply(items[i], args);
88704         }
88705     },
88706
88707     // private
88708     addComponent : function(comp){
88709         this.items.push(comp);
88710         comp.on('destroy', this.removeComponent, this);
88711     },
88712
88713     // private
88714     removeComponent : function(comp){
88715         Ext.Array.remove(this.items, comp);
88716     },
88717
88718     /**
88719      * Executes this Action manually using the handler function specified in the original config object
88720      * or the handler function set with <code>{@link #setHandler}</code>.  Any arguments passed to this
88721      * function will be passed on to the handler function.
88722      * @param {Object...} args (optional) Variable number of arguments passed to the handler function
88723      */
88724     execute : function(){
88725         this.initialConfig.handler.apply(this.initialConfig.scope || Ext.global, arguments);
88726     }
88727 });
88728
88729 /**
88730  * Component layout for editors
88731  * @class Ext.layout.component.Editor
88732  * @extends Ext.layout.component.Component
88733  * @private
88734  */
88735 Ext.define('Ext.layout.component.Editor', {
88736
88737     /* Begin Definitions */
88738
88739     alias: ['layout.editor'],
88740
88741     extend: 'Ext.layout.component.Component',
88742
88743     /* End Definitions */
88744
88745     onLayout: function(width, height) {
88746         var me = this,
88747             owner = me.owner,
88748             autoSize = owner.autoSize;
88749             
88750         if (autoSize === true) {
88751             autoSize = {
88752                 width: 'field',
88753                 height: 'field'    
88754             };
88755         }
88756         
88757         if (autoSize) {
88758             width = me.getDimension(owner, autoSize.width, 'Width', width);
88759             height = me.getDimension(owner, autoSize.height, 'Height', height);
88760         }
88761         me.setTargetSize(width, height);
88762         owner.field.setSize(width, height);
88763     },
88764     
88765     getDimension: function(owner, type, dimension, actual){
88766         var method = 'get' + dimension;
88767         switch (type) {
88768             case 'boundEl':
88769                 return owner.boundEl[method]();
88770             case 'field':
88771                 return owner.field[method]();
88772             default:
88773                 return actual;
88774         }
88775     }
88776 });
88777 /**
88778  * @class Ext.Editor
88779  * @extends Ext.Component
88780  *
88781  * <p>
88782  * The Editor class is used to provide inline editing for elements on the page. The editor
88783  * is backed by a {@link Ext.form.field.Field} that will be displayed to edit the underlying content.
88784  * The editor is a floating Component, when the editor is shown it is automatically aligned to
88785  * display over the top of the bound element it is editing. The Editor contains several options
88786  * for how to handle key presses:
88787  * <ul>
88788  * <li>{@link #completeOnEnter}</li>
88789  * <li>{@link #cancelOnEsc}</li>
88790  * <li>{@link #swallowKeys}</li>
88791  * </ul>
88792  * It also has options for how to use the value once the editor has been activated:
88793  * <ul>
88794  * <li>{@link #revertInvalid}</li>
88795  * <li>{@link #ignoreNoChange}</li>
88796  * <li>{@link #updateEl}</li>
88797  * </ul>
88798  * Sample usage:
88799  * </p>
88800  * <pre><code>
88801 var editor = new Ext.Editor({
88802     updateEl: true, // update the innerHTML of the bound element when editing completes
88803     field: {
88804         xtype: 'textfield'
88805     }
88806 });
88807 var el = Ext.get('my-text'); // The element to 'edit'
88808 editor.startEdit(el); // The value of the field will be taken as the innerHTML of the element.
88809  * </code></pre>
88810  * {@img Ext.Editor/Ext.Editor.png Ext.Editor component}
88811  *
88812  */
88813 Ext.define('Ext.Editor', {
88814
88815     /* Begin Definitions */
88816
88817     extend: 'Ext.Component',
88818
88819     alias: 'widget.editor',
88820
88821     requires: ['Ext.layout.component.Editor'],
88822
88823     /* End Definitions */
88824
88825    componentLayout: 'editor',
88826
88827     /**
88828     * @cfg {Ext.form.field.Field} field
88829     * The Field object (or descendant) or config object for field
88830     */
88831
88832     /**
88833      * @cfg {Boolean} allowBlur
88834      * True to {@link #completeEdit complete the editing process} if in edit mode when the
88835      * field is blurred.
88836      */
88837     allowBlur: true,
88838
88839     /**
88840      * @cfg {Boolean/Object} autoSize
88841      * True for the editor to automatically adopt the size of the underlying field. Otherwise, an object
88842      * can be passed to indicate where to get each dimension. The available properties are 'boundEl' and
88843      * 'field'. If a dimension is not specified, it will use the underlying height/width specified on
88844      * the editor object.
88845      * Examples:
88846      * <pre><code>
88847 autoSize: true // The editor will be sized to the height/width of the field
88848
88849 height: 21,
88850 autoSize: {
88851     width: 'boundEl' // The width will be determined by the width of the boundEl, the height from the editor (21)
88852 }
88853
88854 autoSize: {
88855     width: 'field', // Width from the field
88856     height: 'boundEl' // Height from the boundEl
88857 }
88858      * </pre></code>
88859      */
88860
88861     /**
88862      * @cfg {Boolean} revertInvalid
88863      * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
88864      * validation fails
88865      */
88866     revertInvalid: true,
88867
88868     /**
88869      * @cfg {Boolean} [ignoreNoChange=false]
88870      * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
88871      * the value has not changed.  Applies only to string values - edits for other data types
88872      * will never be ignored.
88873      */
88874
88875     /**
88876      * @cfg {Boolean} [hideEl=true]
88877      * False to keep the bound element visible while the editor is displayed
88878      */
88879
88880     /**
88881      * @cfg {Object} value
88882      * The data value of the underlying field
88883      */
88884     value : '',
88885
88886     /**
88887      * @cfg {String} alignment
88888      * The position to align to (see {@link Ext.Element#alignTo} for more details).
88889      */
88890     alignment: 'c-c?',
88891
88892     /**
88893      * @cfg {Number[]} offsets
88894      * The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details.
88895      */
88896     offsets: [0, 0],
88897
88898     /**
88899      * @cfg {Boolean/String} shadow
88900      * "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" for bottom-right shadow.
88901      */
88902     shadow : 'frame',
88903
88904     /**
88905      * @cfg {Boolean} constrain
88906      * True to constrain the editor to the viewport
88907      */
88908     constrain : false,
88909
88910     /**
88911      * @cfg {Boolean} swallowKeys
88912      * Handle the keydown/keypress events so they don't propagate
88913      */
88914     swallowKeys : true,
88915
88916     /**
88917      * @cfg {Boolean} completeOnEnter
88918      * True to complete the edit when the enter key is pressed.
88919      */
88920     completeOnEnter : true,
88921
88922     /**
88923      * @cfg {Boolean} cancelOnEsc
88924      * True to cancel the edit when the escape key is pressed.
88925      */
88926     cancelOnEsc : true,
88927
88928     /**
88929      * @cfg {Boolean} updateEl
88930      * True to update the innerHTML of the bound element when the update completes
88931      */
88932     updateEl : false,
88933
88934     /**
88935      * @cfg {String/HTMLElement/Ext.Element} parentEl
88936      * An element to render to. Defaults to the <tt>document.body</tt>.
88937      */
88938
88939     // private overrides
88940     hidden: true,
88941     baseCls: Ext.baseCSSPrefix + 'editor',
88942
88943     initComponent : function() {
88944         var me = this,
88945             field = me.field = Ext.ComponentManager.create(me.field, 'textfield');
88946
88947         Ext.apply(field, {
88948             inEditor: true,
88949             msgTarget: field.msgTarget == 'title' ? 'title' :  'qtip'
88950         });
88951         me.mon(field, {
88952             scope: me,
88953             blur: {
88954                 fn: me.onBlur,
88955                 // slight delay to avoid race condition with startEdits (e.g. grid view refresh)
88956                 delay: 1
88957             },
88958             specialkey: me.onSpecialKey
88959         });
88960
88961         if (field.grow) {
88962             me.mon(field, 'autosize', me.onAutoSize,  me, {delay: 1});
88963         }
88964         me.floating = {
88965             constrain: me.constrain
88966         };
88967
88968         me.callParent(arguments);
88969
88970         me.addEvents(
88971             /**
88972              * @event beforestartedit
88973              * Fires when editing is initiated, but before the value changes.  Editing can be canceled by returning
88974              * false from the handler of this event.
88975              * @param {Ext.Editor} this
88976              * @param {Ext.Element} boundEl The underlying element bound to this editor
88977              * @param {Object} value The field value being set
88978              */
88979             'beforestartedit',
88980
88981             /**
88982              * @event startedit
88983              * Fires when this editor is displayed
88984              * @param {Ext.Editor} this
88985              * @param {Ext.Element} boundEl The underlying element bound to this editor
88986              * @param {Object} value The starting field value
88987              */
88988             'startedit',
88989
88990             /**
88991              * @event beforecomplete
88992              * Fires after a change has been made to the field, but before the change is reflected in the underlying
88993              * field.  Saving the change to the field can be canceled by returning false from the handler of this event.
88994              * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
88995              * event will not fire since no edit actually occurred.
88996              * @param {Ext.Editor} this
88997              * @param {Object} value The current field value
88998              * @param {Object} startValue The original field value
88999              */
89000             'beforecomplete',
89001             /**
89002              * @event complete
89003              * Fires after editing is complete and any changed value has been written to the underlying field.
89004              * @param {Ext.Editor} this
89005              * @param {Object} value The current field value
89006              * @param {Object} startValue The original field value
89007              */
89008             'complete',
89009             /**
89010              * @event canceledit
89011              * Fires after editing has been canceled and the editor's value has been reset.
89012              * @param {Ext.Editor} this
89013              * @param {Object} value The user-entered field value that was discarded
89014              * @param {Object} startValue The original field value that was set back into the editor after cancel
89015              */
89016             'canceledit',
89017             /**
89018              * @event specialkey
89019              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check
89020              * {@link Ext.EventObject#getKey} to determine which key was pressed.
89021              * @param {Ext.Editor} this
89022              * @param {Ext.form.field.Field} The field attached to this editor
89023              * @param {Ext.EventObject} event The event object
89024              */
89025             'specialkey'
89026         );
89027     },
89028
89029     // private
89030     onAutoSize: function(){
89031         this.doComponentLayout();
89032     },
89033
89034     // private
89035     onRender : function(ct, position) {
89036         var me = this,
89037             field = me.field,
89038             inputEl = field.inputEl;
89039
89040         me.callParent(arguments);
89041
89042         field.render(me.el);
89043         //field.hide();
89044         // Ensure the field doesn't get submitted as part of any form
89045         if (inputEl) {
89046             inputEl.dom.name = '';
89047             if (me.swallowKeys) {
89048                 inputEl.swallowEvent([
89049                     'keypress', // *** Opera
89050                     'keydown'   // *** all other browsers
89051                 ]);
89052             }
89053         }
89054     },
89055
89056     // private
89057     onSpecialKey : function(field, event) {
89058         var me = this,
89059             key = event.getKey(),
89060             complete = me.completeOnEnter && key == event.ENTER,
89061             cancel = me.cancelOnEsc && key == event.ESC;
89062
89063         if (complete || cancel) {
89064             event.stopEvent();
89065             // Must defer this slightly to prevent exiting edit mode before the field's own
89066             // key nav can handle the enter key, e.g. selecting an item in a combobox list
89067             Ext.defer(function() {
89068                 if (complete) {
89069                     me.completeEdit();
89070                 } else {
89071                     me.cancelEdit();
89072                 }
89073                 if (field.triggerBlur) {
89074                     field.triggerBlur();
89075                 }
89076             }, 10);
89077         }
89078
89079         this.fireEvent('specialkey', this, field, event);
89080     },
89081
89082     /**
89083      * Starts the editing process and shows the editor.
89084      * @param {String/HTMLElement/Ext.Element} el The element to edit
89085      * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
89086       * to the innerHTML of el.
89087      */
89088     startEdit : function(el, value) {
89089         var me = this,
89090             field = me.field;
89091
89092         me.completeEdit();
89093         me.boundEl = Ext.get(el);
89094         value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
89095
89096         if (!me.rendered) {
89097             me.render(me.parentEl || document.body);
89098         }
89099
89100         if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
89101             me.startValue = value;
89102             me.show();
89103             field.reset();
89104             field.setValue(value);
89105             me.realign(true);
89106             field.focus(false, 10);
89107             if (field.autoSize) {
89108                 field.autoSize();
89109             }
89110             me.editing = true;
89111         }
89112     },
89113
89114     /**
89115      * Realigns the editor to the bound field based on the current alignment config value.
89116      * @param {Boolean} autoSize (optional) True to size the field to the dimensions of the bound element.
89117      */
89118     realign : function(autoSize) {
89119         var me = this;
89120         if (autoSize === true) {
89121             me.doComponentLayout();
89122         }
89123         me.alignTo(me.boundEl, me.alignment, me.offsets);
89124     },
89125
89126     /**
89127      * Ends the editing process, persists the changed value to the underlying field, and hides the editor.
89128      * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after edit
89129      */
89130     completeEdit : function(remainVisible) {
89131         var me = this,
89132             field = me.field,
89133             value;
89134
89135         if (!me.editing) {
89136             return;
89137         }
89138
89139         // Assert combo values first
89140         if (field.assertValue) {
89141             field.assertValue();
89142         }
89143
89144         value = me.getValue();
89145         if (!field.isValid()) {
89146             if (me.revertInvalid !== false) {
89147                 me.cancelEdit(remainVisible);
89148             }
89149             return;
89150         }
89151
89152         if (String(value) === String(me.startValue) && me.ignoreNoChange) {
89153             me.hideEdit(remainVisible);
89154             return;
89155         }
89156
89157         if (me.fireEvent('beforecomplete', me, value, me.startValue) !== false) {
89158             // Grab the value again, may have changed in beforecomplete
89159             value = me.getValue();
89160             if (me.updateEl && me.boundEl) {
89161                 me.boundEl.update(value);
89162             }
89163             me.hideEdit(remainVisible);
89164             me.fireEvent('complete', me, value, me.startValue);
89165         }
89166     },
89167
89168     // private
89169     onShow : function() {
89170         var me = this;
89171
89172         me.callParent(arguments);
89173         if (me.hideEl !== false) {
89174             me.boundEl.hide();
89175         }
89176         me.fireEvent("startedit", me.boundEl, me.startValue);
89177     },
89178
89179     /**
89180      * Cancels the editing process and hides the editor without persisting any changes.  The field value will be
89181      * reverted to the original starting value.
89182      * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after cancel
89183      */
89184     cancelEdit : function(remainVisible) {
89185         var me = this,
89186             startValue = me.startValue,
89187             value;
89188
89189         if (me.editing) {
89190             value = me.getValue();
89191             me.setValue(startValue);
89192             me.hideEdit(remainVisible);
89193             me.fireEvent('canceledit', me, value, startValue);
89194         }
89195     },
89196
89197     // private
89198     hideEdit: function(remainVisible) {
89199         if (remainVisible !== true) {
89200             this.editing = false;
89201             this.hide();
89202         }
89203     },
89204
89205     // private
89206     onBlur : function() {
89207         var me = this;
89208
89209         // selectSameEditor flag allows the same editor to be started without onBlur firing on itself
89210         if(me.allowBlur === true && me.editing && me.selectSameEditor !== true) {
89211             me.completeEdit();
89212         }
89213     },
89214
89215     // private
89216     onHide : function() {
89217         var me = this,
89218             field = me.field;
89219
89220         if (me.editing) {
89221             me.completeEdit();
89222             return;
89223         }
89224         field.blur();
89225         if (field.collapse) {
89226             field.collapse();
89227         }
89228
89229         //field.hide();
89230         if (me.hideEl !== false) {
89231             me.boundEl.show();
89232         }
89233         me.callParent(arguments);
89234     },
89235
89236     /**
89237      * Sets the data value of the editor
89238      * @param {Object} value Any valid value supported by the underlying field
89239      */
89240     setValue : function(value) {
89241         this.field.setValue(value);
89242     },
89243
89244     /**
89245      * Gets the data value of the editor
89246      * @return {Object} The data value
89247      */
89248     getValue : function() {
89249         return this.field.getValue();
89250     },
89251
89252     beforeDestroy : function() {
89253         var me = this;
89254
89255         Ext.destroy(me.field);
89256         delete me.field;
89257         delete me.parentEl;
89258         delete me.boundEl;
89259
89260         me.callParent(arguments);
89261     }
89262 });
89263 /**
89264  * @class Ext.Img
89265  * @extends Ext.Component
89266  *
89267  * Simple helper class for easily creating image components. This simply renders an image tag to the DOM
89268  * with the configured src.
89269  *
89270  * {@img Ext.Img/Ext.Img.png Ext.Img component}
89271  *
89272  * ## Example usage: 
89273  *
89274  *     var changingImage = Ext.create('Ext.Img', {
89275  *         src: 'http://www.sencha.com/img/20110215-feat-html5.png',
89276  *         renderTo: Ext.getBody()
89277  *     });
89278  *      
89279  *     // change the src of the image programmatically
89280  *     changingImage.setSrc('http://www.sencha.com/img/20110215-feat-perf.png');
89281 */
89282 Ext.define('Ext.Img', {
89283     extend: 'Ext.Component',
89284     alias: ['widget.image', 'widget.imagecomponent'],
89285     /** @cfg {String} src The image src */
89286     src: '',
89287
89288     getElConfig: function() {
89289         return {
89290             tag: 'img',
89291             src: this.src
89292         };
89293     },
89294     
89295     // null out this function, we can't set any html inside the image
89296     initRenderTpl: Ext.emptyFn,
89297     
89298     /**
89299      * Updates the {@link #src} of the image
89300      */
89301     setSrc: function(src) {
89302         var me = this,
89303             img = me.el;
89304         me.src = src;
89305         if (img) {
89306             img.dom.src = src;
89307         }
89308     }
89309 });
89310
89311 /**
89312  * @class Ext.Layer
89313  * @extends Ext.Element
89314  * An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
89315  * automatic maintaining of shadow/shim positions.
89316  *
89317  * @cfg {Boolean} [shim=true]
89318  * False to disable the iframe shim in browsers which need one.
89319  *
89320  * @cfg {String/Boolean} [shadow=false]
89321  * True to automatically create an {@link Ext.Shadow}, or a string indicating the
89322  * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow.
89323  *
89324  * @cfg {Object} [dh={tag: 'div', cls: 'x-layer'}]
89325  * DomHelper object config to create element with.
89326  *
89327  * @cfg {Boolean} [constrain=true]
89328  * False to disable constrain to viewport.
89329  *
89330  * @cfg {String} cls
89331  * CSS class to add to the element
89332  *
89333  * @cfg {Number} [zindex=11000]
89334  * Starting z-index.
89335  *
89336  * @cfg {Number} [shadowOffset=4]
89337  * Number of pixels to offset the shadow
89338  *
89339  * @cfg {Boolean} [useDisplay=false]
89340  * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
89341  * to use css style <tt>'display:none;'</tt> to hide the Layer.
89342  *
89343  * @cfg {String} visibilityCls
89344  * The CSS class name to add in order to hide this Layer if this layer
89345  * is configured with <code>{@link #hideMode}: 'asclass'</code>
89346  *
89347  * @cfg {String} hideMode
89348  * A String which specifies how this Layer will be hidden.
89349  * Values may be<div class="mdetail-params"><ul>
89350  * <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
89351  * <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
89352  * <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
89353  * is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
89354  * in a Component having zero dimensions.</li></ul></div>
89355  */
89356 Ext.define('Ext.Layer', {
89357     uses: ['Ext.Shadow'],
89358
89359     // shims are shared among layer to keep from having 100 iframes
89360     statics: {
89361         shims: []
89362     },
89363
89364     extend: 'Ext.Element',
89365
89366     /**
89367      * Creates new Layer.
89368      * @param {Object} config (optional) An object with config options.
89369      * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element.
89370      * If the element is not found it creates it.
89371      */
89372     constructor: function(config, existingEl) {
89373         config = config || {};
89374         var me = this,
89375             dh = Ext.DomHelper,
89376             cp = config.parentEl,
89377             pel = cp ? Ext.getDom(cp) : document.body,
89378         hm = config.hideMode;
89379
89380         if (existingEl) {
89381             me.dom = Ext.getDom(existingEl);
89382         }
89383         if (!me.dom) {
89384             me.dom = dh.append(pel, config.dh || {
89385                 tag: 'div',
89386                 cls: Ext.baseCSSPrefix + 'layer'
89387             });
89388         } else {
89389             me.addCls(Ext.baseCSSPrefix + 'layer');
89390             if (!me.dom.parentNode) {
89391                 pel.appendChild(me.dom);
89392             }
89393         }
89394
89395         if (config.cls) {
89396             me.addCls(config.cls);
89397         }
89398         me.constrain = config.constrain !== false;
89399
89400         // Allow Components to pass their hide mode down to the Layer if they are floating.
89401         // Otherwise, allow useDisplay to override the default hiding method which is visibility.
89402         // TODO: Have ExtJS's Element implement visibilityMode by using classes as in Mobile.
89403         if (hm) {
89404             me.setVisibilityMode(Ext.Element[hm.toUpperCase()]);
89405             if (me.visibilityMode == Ext.Element.ASCLASS) {
89406                 me.visibilityCls = config.visibilityCls;
89407             }
89408         } else if (config.useDisplay) {
89409             me.setVisibilityMode(Ext.Element.DISPLAY);
89410         } else {
89411             me.setVisibilityMode(Ext.Element.VISIBILITY);
89412         }
89413
89414         if (config.id) {
89415             me.id = me.dom.id = config.id;
89416         } else {
89417             me.id = Ext.id(me.dom);
89418         }
89419         me.position('absolute');
89420         if (config.shadow) {
89421             me.shadowOffset = config.shadowOffset || 4;
89422             me.shadow = Ext.create('Ext.Shadow', {
89423                 offset: me.shadowOffset,
89424                 mode: config.shadow
89425             });
89426             me.disableShadow();
89427         } else {
89428             me.shadowOffset = 0;
89429         }
89430         me.useShim = config.shim !== false && Ext.useShims;
89431         if (config.hidden === true) {
89432             me.hide();
89433         } else {
89434             me.show();
89435         }
89436     },
89437
89438     getZIndex: function() {
89439         return parseInt((this.getShim() || this).getStyle('z-index'), 10);
89440     },
89441
89442     getShim: function() {
89443         var me = this,
89444             shim, pn;
89445
89446         if (!me.useShim) {
89447             return null;
89448         }
89449         if (!me.shim) {
89450             shim = me.self.shims.shift();
89451             if (!shim) {
89452                 shim = me.createShim();
89453                 shim.enableDisplayMode('block');
89454                 shim.hide();
89455             }
89456             pn = me.dom.parentNode;
89457             if (shim.dom.parentNode != pn) {
89458                 pn.insertBefore(shim.dom, me.dom);
89459             }
89460             me.shim = shim;
89461         }
89462         return me.shim;
89463     },
89464
89465     hideShim: function() {
89466         var me = this;
89467         
89468         if (me.shim) {
89469             me.shim.setDisplayed(false);
89470             me.self.shims.push(me.shim);
89471             delete me.shim;
89472         }
89473     },
89474
89475     disableShadow: function() {
89476         var me = this;
89477         
89478         if (me.shadow && !me.shadowDisabled) {
89479             me.shadowDisabled = true;
89480             me.shadow.hide();
89481             me.lastShadowOffset = me.shadowOffset;
89482             me.shadowOffset = 0;
89483         }
89484     },
89485
89486     enableShadow: function(show) {
89487         var me = this;
89488         
89489         if (me.shadow && me.shadowDisabled) {
89490             me.shadowDisabled = false;
89491             me.shadowOffset = me.lastShadowOffset;
89492             delete me.lastShadowOffset;
89493             if (show) {
89494                 me.sync(true);
89495             }
89496         }
89497     },
89498
89499     /**
89500      * @private
89501      * <p>Synchronize this Layer's associated elements, the shadow, and possibly the shim.</p>
89502      * <p>This code can execute repeatedly in milliseconds,
89503      * eg: dragging a Component configured liveDrag: true, or which has no ghost method
89504      * so code size was sacrificed for efficiency (e.g. no getBox/setBox, no XY calls)</p>
89505      * @param {Boolean} doShow Pass true to ensure that the shadow is shown.
89506      */
89507     sync: function(doShow) {
89508         var me = this,
89509             shadow = me.shadow,
89510             shadowPos, shimStyle, shadowSize;
89511
89512         if (!me.updating && me.isVisible() && (shadow || me.useShim)) {
89513             var shim = me.getShim(),
89514                 l = me.getLeft(true),
89515                 t = me.getTop(true),
89516                 w = me.dom.offsetWidth,
89517                 h = me.dom.offsetHeight,
89518                 shimIndex;
89519
89520             if (shadow && !me.shadowDisabled) {
89521                 if (doShow && !shadow.isVisible()) {
89522                     shadow.show(me);
89523                 } else {
89524                     shadow.realign(l, t, w, h);
89525                 }
89526                 if (shim) {
89527                     // TODO: Determine how the shims zIndex is above the layer zIndex at this point
89528                     shimIndex = shim.getStyle('z-index');
89529                     if (shimIndex > me.zindex) {
89530                         me.shim.setStyle('z-index', me.zindex - 2);
89531                     }
89532                     shim.show();
89533                     // fit the shim behind the shadow, so it is shimmed too
89534                     if (shadow.isVisible()) {
89535                         shadowPos = shadow.el.getXY();
89536                         shimStyle = shim.dom.style;
89537                         shadowSize = shadow.el.getSize();
89538                         if (Ext.supports.CSS3BoxShadow) {
89539                             shadowSize.height += 6;
89540                             shadowSize.width += 4;
89541                             shadowPos[0] -= 2;
89542                             shadowPos[1] -= 4;
89543                         }
89544                         shimStyle.left = (shadowPos[0]) + 'px';
89545                         shimStyle.top = (shadowPos[1]) + 'px';
89546                         shimStyle.width = (shadowSize.width) + 'px';
89547                         shimStyle.height = (shadowSize.height) + 'px';
89548                     } else {
89549                         shim.setSize(w, h);
89550                         shim.setLeftTop(l, t);
89551                     }
89552                 }
89553             } else if (shim) {
89554                 // TODO: Determine how the shims zIndex is above the layer zIndex at this point
89555                 shimIndex = shim.getStyle('z-index');
89556                 if (shimIndex > me.zindex) {
89557                     me.shim.setStyle('z-index', me.zindex - 2);
89558                 }
89559                 shim.show();
89560                 shim.setSize(w, h);
89561                 shim.setLeftTop(l, t);
89562             }
89563         }
89564         return me;
89565     },
89566
89567     remove: function() {
89568         this.hideUnders();
89569         this.callParent();
89570     },
89571
89572     // private
89573     beginUpdate: function() {
89574         this.updating = true;
89575     },
89576
89577     // private
89578     endUpdate: function() {
89579         this.updating = false;
89580         this.sync(true);
89581     },
89582
89583     // private
89584     hideUnders: function() {
89585         if (this.shadow) {
89586             this.shadow.hide();
89587         }
89588         this.hideShim();
89589     },
89590
89591     // private
89592     constrainXY: function() {
89593         if (this.constrain) {
89594             var vw = Ext.Element.getViewWidth(),
89595                 vh = Ext.Element.getViewHeight(),
89596                 s = Ext.getDoc().getScroll(),
89597                 xy = this.getXY(),
89598                 x = xy[0],
89599                 y = xy[1],
89600                 so = this.shadowOffset,
89601                 w = this.dom.offsetWidth + so,
89602                 h = this.dom.offsetHeight + so,
89603                 moved = false; // only move it if it needs it
89604             // first validate right/bottom
89605             if ((x + w) > vw + s.left) {
89606                 x = vw - w - so;
89607                 moved = true;
89608             }
89609             if ((y + h) > vh + s.top) {
89610                 y = vh - h - so;
89611                 moved = true;
89612             }
89613             // then make sure top/left isn't negative
89614             if (x < s.left) {
89615                 x = s.left;
89616                 moved = true;
89617             }
89618             if (y < s.top) {
89619                 y = s.top;
89620                 moved = true;
89621             }
89622             if (moved) {
89623                 Ext.Layer.superclass.setXY.call(this, [x, y]);
89624                 this.sync();
89625             }
89626         }
89627         return this;
89628     },
89629
89630     getConstrainOffset: function() {
89631         return this.shadowOffset;
89632     },
89633
89634     // overridden Element method
89635     setVisible: function(visible, animate, duration, callback, easing) {
89636         var me = this,
89637             cb;
89638
89639         // post operation processing
89640         cb = function() {
89641             if (visible) {
89642                 me.sync(true);
89643             }
89644             if (callback) {
89645                 callback();
89646             }
89647         };
89648
89649         // Hide shadow and shim if hiding
89650         if (!visible) {
89651             me.hideUnders(true);
89652         }
89653         me.callParent([visible, animate, duration, callback, easing]);
89654         if (!animate) {
89655             cb();
89656         }
89657         return me;
89658     },
89659
89660     // private
89661     beforeFx: function() {
89662         this.beforeAction();
89663         return this.callParent(arguments);
89664     },
89665
89666     // private
89667     afterFx: function() {
89668         this.callParent(arguments);
89669         this.sync(this.isVisible());
89670     },
89671
89672     // private
89673     beforeAction: function() {
89674         if (!this.updating && this.shadow) {
89675             this.shadow.hide();
89676         }
89677     },
89678
89679     // overridden Element method
89680     setLeft: function(left) {
89681         this.callParent(arguments);
89682         return this.sync();
89683     },
89684
89685     setTop: function(top) {
89686         this.callParent(arguments);
89687         return this.sync();
89688     },
89689
89690     setLeftTop: function(left, top) {
89691         this.callParent(arguments);
89692         return this.sync();
89693     },
89694
89695     setXY: function(xy, animate, duration, callback, easing) {
89696         var me = this;
89697         
89698         // Callback will restore shadow state and call the passed callback
89699         callback = me.createCB(callback);
89700
89701         me.fixDisplay();
89702         me.beforeAction();
89703         me.callParent([xy, animate, duration, callback, easing]);
89704         if (!animate) {
89705             callback();
89706         }
89707         return me;
89708     },
89709
89710     // private
89711     createCB: function(callback) {
89712         var me = this,
89713             showShadow = me.shadow && me.shadow.isVisible();
89714
89715         return function() {
89716             me.constrainXY();
89717             me.sync(showShadow);
89718             if (callback) {
89719                 callback();
89720             }
89721         };
89722     },
89723
89724     // overridden Element method
89725     setX: function(x, animate, duration, callback, easing) {
89726         this.setXY([x, this.getY()], animate, duration, callback, easing);
89727         return this;
89728     },
89729
89730     // overridden Element method
89731     setY: function(y, animate, duration, callback, easing) {
89732         this.setXY([this.getX(), y], animate, duration, callback, easing);
89733         return this;
89734     },
89735
89736     // overridden Element method
89737     setSize: function(w, h, animate, duration, callback, easing) {
89738         var me = this;
89739         
89740         // Callback will restore shadow state and call the passed callback
89741         callback = me.createCB(callback);
89742
89743         me.beforeAction();
89744         me.callParent([w, h, animate, duration, callback, easing]);
89745         if (!animate) {
89746             callback();
89747         }
89748         return me;
89749     },
89750
89751     // overridden Element method
89752     setWidth: function(w, animate, duration, callback, easing) {
89753         var me = this;
89754         
89755         // Callback will restore shadow state and call the passed callback
89756         callback = me.createCB(callback);
89757
89758         me.beforeAction();
89759         me.callParent([w, animate, duration, callback, easing]);
89760         if (!animate) {
89761             callback();
89762         }
89763         return me;
89764     },
89765
89766     // overridden Element method
89767     setHeight: function(h, animate, duration, callback, easing) {
89768         var me = this;
89769         
89770         // Callback will restore shadow state and call the passed callback
89771         callback = me.createCB(callback);
89772
89773         me.beforeAction();
89774         me.callParent([h, animate, duration, callback, easing]);
89775         if (!animate) {
89776             callback();
89777         }
89778         return me;
89779     },
89780
89781     // overridden Element method
89782     setBounds: function(x, y, width, height, animate, duration, callback, easing) {
89783         var me = this;
89784         
89785         // Callback will restore shadow state and call the passed callback
89786         callback = me.createCB(callback);
89787
89788         me.beforeAction();
89789         if (!animate) {
89790             Ext.Layer.superclass.setXY.call(me, [x, y]);
89791             Ext.Layer.superclass.setSize.call(me, width, height);
89792             callback();
89793         } else {
89794             me.callParent([x, y, width, height, animate, duration, callback, easing]);
89795         }
89796         return me;
89797     },
89798
89799     /**
89800      * <p>Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
89801      * incremented depending upon the presence of a shim or a shadow in so that it always shows above those two associated elements.</p>
89802      * <p>Any shim, will be assigned the passed z-index. A shadow will be assigned the next highet z-index, and the Layer's
89803      * element will receive the highest  z-index.
89804      * @param {Number} zindex The new z-index to set
89805      * @return {Ext.Layer} The Layer
89806      */
89807     setZIndex: function(zindex) {
89808         var me = this;
89809         
89810         me.zindex = zindex;
89811         if (me.getShim()) {
89812             me.shim.setStyle('z-index', zindex++);
89813         }
89814         if (me.shadow) {
89815             me.shadow.setZIndex(zindex++);
89816         }
89817         return me.setStyle('z-index', zindex);
89818     },
89819     
89820     setOpacity: function(opacity){
89821         if (this.shadow) {
89822             this.shadow.setOpacity(opacity);
89823         }
89824         return this.callParent(arguments);
89825     }
89826 });
89827
89828 /**
89829  * @class Ext.layout.component.ProgressBar
89830  * @extends Ext.layout.component.Component
89831  * @private
89832  */
89833
89834 Ext.define('Ext.layout.component.ProgressBar', {
89835
89836     /* Begin Definitions */
89837
89838     alias: ['layout.progressbar'],
89839
89840     extend: 'Ext.layout.component.Component',
89841
89842     /* End Definitions */
89843
89844     type: 'progressbar',
89845
89846     onLayout: function(width, height) {
89847         var me = this,
89848             owner = me.owner,
89849             textEl = owner.textEl;
89850         
89851         me.setElementSize(owner.el, width, height);
89852         textEl.setWidth(owner.el.getWidth(true));
89853         
89854         me.callParent([width, height]);
89855         
89856         owner.updateProgress(owner.value);
89857     }
89858 });
89859 /**
89860  * An updateable progress bar component. The progress bar supports two different modes: manual and automatic.
89861  *
89862  * In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the progress bar
89863  * as needed from your own code. This method is most appropriate when you want to show progress throughout an operation
89864  * that has predictable points of interest at which you can update the control.
89865  *
89866  * In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it once the
89867  * operation is complete. You can optionally have the progress bar wait for a specific amount of time and then clear
89868  * itself. Automatic mode is most appropriate for timed operations or asynchronous operations in which you have no need
89869  * for indicating intermediate progress.
89870  *
89871  *     @example
89872  *     var p = Ext.create('Ext.ProgressBar', {
89873  *        renderTo: Ext.getBody(),
89874  *        width: 300
89875  *     });
89876  *
89877  *     // Wait for 5 seconds, then update the status el (progress bar will auto-reset)
89878  *     p.wait({
89879  *         interval: 500, //bar will move fast!
89880  *         duration: 50000,
89881  *         increment: 15,
89882  *         text: 'Updating...',
89883  *         scope: this,
89884  *         fn: function(){
89885  *             p.updateText('Done!');
89886  *         }
89887  *     });
89888  */
89889 Ext.define('Ext.ProgressBar', {
89890     extend: 'Ext.Component',
89891     alias: 'widget.progressbar',
89892
89893     requires: [
89894         'Ext.Template',
89895         'Ext.CompositeElement',
89896         'Ext.TaskManager',
89897         'Ext.layout.component.ProgressBar'
89898     ],
89899
89900     uses: ['Ext.fx.Anim'],
89901
89902    /**
89903     * @cfg {Number} [value=0]
89904     * A floating point value between 0 and 1 (e.g., .5)
89905     */
89906
89907    /**
89908     * @cfg {String} [text='']
89909     * The progress bar text (defaults to '')
89910     */
89911
89912    /**
89913     * @cfg {String/HTMLElement/Ext.Element} textEl
89914     * The element to render the progress text to (defaults to the progress bar's internal text element)
89915     */
89916
89917    /**
89918     * @cfg {String} id
89919     * The progress bar element's id (defaults to an auto-generated id)
89920     */
89921
89922    /**
89923     * @cfg {String} [baseCls='x-progress']
89924     * The base CSS class to apply to the progress bar's wrapper element.
89925     */
89926     baseCls: Ext.baseCSSPrefix + 'progress',
89927
89928     config: {
89929         /**
89930         * @cfg {Boolean} animate
89931         * True to animate the progress bar during transitions
89932         */
89933         animate: false,
89934
89935         /**
89936          * @cfg {String} text
89937          * The text shown in the progress bar
89938          */
89939         text: ''
89940     },
89941
89942     // private
89943     waitTimer: null,
89944
89945     renderTpl: [
89946         '<div class="{baseCls}-text {baseCls}-text-back">',
89947             '<div>&#160;</div>',
89948         '</div>',
89949         '<div id="{id}-bar" class="{baseCls}-bar">',
89950             '<div class="{baseCls}-text">',
89951                 '<div>&#160;</div>',
89952             '</div>',
89953         '</div>'
89954     ],
89955
89956     componentLayout: 'progressbar',
89957
89958     // private
89959     initComponent: function() {
89960         this.callParent();
89961
89962         this.addChildEls('bar');
89963
89964         this.addEvents(
89965             /**
89966              * @event update
89967              * Fires after each update interval
89968              * @param {Ext.ProgressBar} this
89969              * @param {Number} value The current progress value
89970              * @param {String} text The current progress text
89971              */
89972             "update"
89973         );
89974     },
89975
89976     afterRender : function() {
89977         var me = this;
89978
89979         // This produces a composite w/2 el's (which is why we cannot use childEls or
89980         // renderSelectors):
89981         me.textEl = me.textEl ? Ext.get(me.textEl) : me.el.select('.' + me.baseCls + '-text');
89982
89983         me.callParent(arguments);
89984
89985         if (me.value) {
89986             me.updateProgress(me.value, me.text);
89987         }
89988         else {
89989             me.updateText(me.text);
89990         }
89991     },
89992
89993     /**
89994      * Updates the progress bar value, and optionally its text. If the text argument is not specified, any existing text
89995      * value will be unchanged. To blank out existing text, pass ''. Note that even if the progress bar value exceeds 1,
89996      * it will never automatically reset -- you are responsible for determining when the progress is complete and
89997      * calling {@link #reset} to clear and/or hide the control.
89998      * @param {Number} [value=0] A floating point value between 0 and 1 (e.g., .5)
89999      * @param {String} [text=''] The string to display in the progress text element
90000      * @param {Boolean} [animate=false] Whether to animate the transition of the progress bar. If this value is not
90001      * specified, the default for the class is used
90002      * @return {Ext.ProgressBar} this
90003      */
90004     updateProgress: function(value, text, animate) {
90005         var me = this,
90006             newWidth;
90007             
90008         me.value = value || 0;
90009         if (text) {
90010             me.updateText(text);
90011         }
90012         if (me.rendered && !me.isDestroyed) {
90013             if (me.isVisible(true)) {
90014                 newWidth = Math.floor(me.value * me.el.getWidth(true));
90015                 if (Ext.isForcedBorderBox) {
90016                     newWidth += me.bar.getBorderWidth("lr");
90017                 }
90018                 if (animate === true || (animate !== false && me.animate)) {
90019                     me.bar.stopAnimation();
90020                     me.bar.animate(Ext.apply({
90021                         to: {
90022                             width: newWidth + 'px'
90023                         }
90024                     }, me.animate));
90025                 } else {
90026                     me.bar.setWidth(newWidth);
90027                 }
90028             } else {
90029                 // force a layout when we're visible again
90030                 me.doComponentLayout();
90031             }
90032         }
90033         me.fireEvent('update', me, me.value, text);
90034         return me;
90035     },
90036
90037     /**
90038      * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress bar itself will
90039      * display the updated text.
90040      * @param {String} [text=''] The string to display in the progress text element
90041      * @return {Ext.ProgressBar} this
90042      */
90043     updateText: function(text) {
90044         var me = this;
90045         
90046         me.text = text;
90047         if (me.rendered) {
90048             me.textEl.update(me.text);
90049         }
90050         return me;
90051     },
90052
90053     applyText : function(text) {
90054         this.updateText(text);
90055     },
90056
90057     /**
90058      * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress bar will
90059      * automatically reset after a fixed amount of time and optionally call a callback function if specified. If no
90060      * duration is passed in, then the progress bar will run indefinitely and must be manually cleared by calling
90061      * {@link #reset}.
90062      *
90063      * Example usage:
90064      *
90065      *     var p = new Ext.ProgressBar({
90066      *        renderTo: 'my-el'
90067      *     });
90068      *
90069      *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
90070      *     var p = Ext.create('Ext.ProgressBar', {
90071      *        renderTo: Ext.getBody(),
90072      *        width: 300
90073      *     });
90074      *
90075      *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
90076      *     p.wait({
90077      *        interval: 500, //bar will move fast!
90078      *        duration: 50000,
90079      *        increment: 15,
90080      *        text: 'Updating...',
90081      *        scope: this,
90082      *        fn: function(){
90083      *           p.updateText('Done!');
90084      *        }
90085      *     });
90086      *
90087      *     //Or update indefinitely until some async action completes, then reset manually
90088      *     p.wait();
90089      *     myAction.on('complete', function(){
90090      *         p.reset();
90091      *         p.updateText('Done!');
90092      *     });
90093      *
90094      * @param {Object} config (optional) Configuration options
90095      * @param {Number} config.duration The length of time in milliseconds that the progress bar should
90096      * run before resetting itself (defaults to undefined, in which case it will run indefinitely
90097      * until reset is called)
90098      * @param {Number} config.interval The length of time in milliseconds between each progress update
90099      * (defaults to 1000 ms)
90100      * @param {Boolean} config.animate Whether to animate the transition of the progress bar. If this
90101      * value is not specified, the default for the class is used.
90102      * @param {Number} config.increment The number of progress update segments to display within the
90103      * progress bar (defaults to 10).  If the bar reaches the end and is still updating, it will
90104      * automatically wrap back to the beginning.
90105      * @param {String} config.text Optional text to display in the progress bar element (defaults to '').
90106      * @param {Function} config.fn A callback function to execute after the progress bar finishes auto-
90107      * updating.  The function will be called with no arguments.  This function will be ignored if
90108      * duration is not specified since in that case the progress bar can only be stopped programmatically,
90109      * so any required function should be called by the same code after it resets the progress bar.
90110      * @param {Object} config.scope The scope that is passed to the callback function (only applies when
90111      * duration and fn are both passed).
90112      * @return {Ext.ProgressBar} this
90113      */
90114     wait: function(o) {
90115         var me = this;
90116             
90117         if (!me.waitTimer) {
90118             scope = me;
90119             o = o || {};
90120             me.updateText(o.text);
90121             me.waitTimer = Ext.TaskManager.start({
90122                 run: function(i){
90123                     var inc = o.increment || 10;
90124                     i -= 1;
90125                     me.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
90126                 },
90127                 interval: o.interval || 1000,
90128                 duration: o.duration,
90129                 onStop: function(){
90130                     if (o.fn) {
90131                         o.fn.apply(o.scope || me);
90132                     }
90133                     me.reset();
90134                 },
90135                 scope: scope
90136             });
90137         }
90138         return me;
90139     },
90140
90141     /**
90142      * Returns true if the progress bar is currently in a {@link #wait} operation
90143      * @return {Boolean} True if waiting, else false
90144      */
90145     isWaiting: function(){
90146         return this.waitTimer !== null;
90147     },
90148
90149     /**
90150      * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress bar will also be hidden
90151      * (using the {@link #hideMode} property internally).
90152      * @param {Boolean} [hide=false] True to hide the progress bar.
90153      * @return {Ext.ProgressBar} this
90154      */
90155     reset: function(hide){
90156         var me = this;
90157         
90158         me.updateProgress(0);
90159         me.clearTimer();
90160         if (hide === true) {
90161             me.hide();
90162         }
90163         return me;
90164     },
90165
90166     // private
90167     clearTimer: function(){
90168         var me = this;
90169         
90170         if (me.waitTimer) {
90171             me.waitTimer.onStop = null; //prevent recursion
90172             Ext.TaskManager.stop(me.waitTimer);
90173             me.waitTimer = null;
90174         }
90175     },
90176
90177     onDestroy: function(){
90178         var me = this;
90179         
90180         me.clearTimer();
90181         if (me.rendered) {
90182             if (me.textEl.isComposite) {
90183                 me.textEl.clear();
90184             }
90185             Ext.destroyMembers(me, 'textEl', 'progressBar');
90186         }
90187         me.callParent();
90188     }
90189 });
90190
90191 /**
90192  * Private utility class that manages the internal Shadow cache
90193  * @private
90194  */
90195 Ext.define('Ext.ShadowPool', {
90196     singleton: true,
90197     requires: ['Ext.DomHelper'],
90198
90199     markup: function() {
90200         if (Ext.supports.CSS3BoxShadow) {
90201             return '<div class="' + Ext.baseCSSPrefix + 'css-shadow" role="presentation"></div>';
90202         } else if (Ext.isIE) {
90203             return '<div class="' + Ext.baseCSSPrefix + 'ie-shadow" role="presentation"></div>';
90204         } else {
90205             return '<div class="' + Ext.baseCSSPrefix + 'frame-shadow" role="presentation">' +
90206                 '<div class="xst" role="presentation">' +
90207                     '<div class="xstl" role="presentation"></div>' +
90208                     '<div class="xstc" role="presentation"></div>' +
90209                     '<div class="xstr" role="presentation"></div>' +
90210                 '</div>' +
90211                 '<div class="xsc" role="presentation">' +
90212                     '<div class="xsml" role="presentation"></div>' +
90213                     '<div class="xsmc" role="presentation"></div>' +
90214                     '<div class="xsmr" role="presentation"></div>' +
90215                 '</div>' +
90216                 '<div class="xsb" role="presentation">' +
90217                     '<div class="xsbl" role="presentation"></div>' +
90218                     '<div class="xsbc" role="presentation"></div>' +
90219                     '<div class="xsbr" role="presentation"></div>' +
90220                 '</div>' +
90221             '</div>';
90222         }
90223     }(),
90224
90225     shadows: [],
90226
90227     pull: function() {
90228         var sh = this.shadows.shift();
90229         if (!sh) {
90230             sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, this.markup));
90231             sh.autoBoxAdjust = false;
90232         }
90233         return sh;
90234     },
90235
90236     push: function(sh) {
90237         this.shadows.push(sh);
90238     },
90239     
90240     reset: function() {
90241         Ext.Array.each(this.shadows, function(shadow) {
90242             shadow.remove();
90243         });
90244         this.shadows = [];
90245     }
90246 });
90247 /**
90248  * @class Ext.Shadow
90249  * Simple class that can provide a shadow effect for any element.  Note that the element MUST be absolutely positioned,
90250  * and the shadow does not provide any shimming.  This should be used only in simple cases -- for more advanced
90251  * functionality that can also provide the same shadow effect, see the {@link Ext.Layer} class.
90252  */
90253 Ext.define('Ext.Shadow', {
90254     requires: ['Ext.ShadowPool'],
90255
90256     /**
90257      * Creates new Shadow.
90258      * @param {Object} config (optional) Config object.
90259      */
90260     constructor: function(config) {
90261         var me = this,
90262             adjusts = {
90263                 h: 0
90264             },
90265             offset,
90266             rad;
90267         
90268         Ext.apply(me, config);
90269         if (!Ext.isString(me.mode)) {
90270             me.mode = me.defaultMode;
90271         }
90272         offset = me.offset;
90273         rad = Math.floor(offset / 2);
90274         me.opacity = 50;
90275         switch (me.mode.toLowerCase()) {
90276             // all this hideous nonsense calculates the various offsets for shadows
90277             case "drop":
90278                 if (Ext.supports.CSS3BoxShadow) {
90279                     adjusts.w = adjusts.h = -offset;
90280                     adjusts.l = adjusts.t = offset;
90281                 } else {
90282                     adjusts.w = 0;
90283                     adjusts.l = adjusts.t = offset;
90284                     adjusts.t -= 1;
90285                     if (Ext.isIE) {
90286                         adjusts.l -= offset + rad;
90287                         adjusts.t -= offset + rad;
90288                         adjusts.w -= rad;
90289                         adjusts.h -= rad;
90290                         adjusts.t += 1;
90291                     }
90292                 }
90293                 break;
90294             case "sides":
90295                 if (Ext.supports.CSS3BoxShadow) {
90296                     adjusts.h -= offset;
90297                     adjusts.t = offset;
90298                     adjusts.l = adjusts.w = 0;
90299                 } else {
90300                     adjusts.w = (offset * 2);
90301                     adjusts.l = -offset;
90302                     adjusts.t = offset - 1;
90303                     if (Ext.isIE) {
90304                         adjusts.l -= (offset - rad);
90305                         adjusts.t -= offset + rad;
90306                         adjusts.l += 1;
90307                         adjusts.w -= (offset - rad) * 2;
90308                         adjusts.w -= rad + 1;
90309                         adjusts.h -= 1;
90310                     }
90311                 }
90312                 break;
90313             case "frame":
90314                 if (Ext.supports.CSS3BoxShadow) {
90315                     adjusts.l = adjusts.w = adjusts.t = 0;
90316                 } else {
90317                     adjusts.w = adjusts.h = (offset * 2);
90318                     adjusts.l = adjusts.t = -offset;
90319                     adjusts.t += 1;
90320                     adjusts.h -= 2;
90321                     if (Ext.isIE) {
90322                         adjusts.l -= (offset - rad);
90323                         adjusts.t -= (offset - rad);
90324                         adjusts.l += 1;
90325                         adjusts.w -= (offset + rad + 1);
90326                         adjusts.h -= (offset + rad);
90327                         adjusts.h += 1;
90328                     }
90329                     break;
90330                 }
90331         }
90332         me.adjusts = adjusts;
90333     },
90334
90335     /**
90336      * @cfg {String} mode
90337      * The shadow display mode.  Supports the following options:<div class="mdetail-params"><ul>
90338      * <li><b><tt>sides</tt></b> : Shadow displays on both sides and bottom only</li>
90339      * <li><b><tt>frame</tt></b> : Shadow displays equally on all four sides</li>
90340      * <li><b><tt>drop</tt></b> : Traditional bottom-right drop shadow</li>
90341      * </ul></div>
90342      */
90343     /**
90344      * @cfg {Number} offset
90345      * The number of pixels to offset the shadow from the element
90346      */
90347     offset: 4,
90348
90349     // private
90350     defaultMode: "drop",
90351
90352     /**
90353      * Displays the shadow under the target element
90354      * @param {String/HTMLElement/Ext.Element} targetEl The id or element under which the shadow should display
90355      */
90356     show: function(target) {
90357         var me = this,
90358             index;
90359         
90360         target = Ext.get(target);
90361         if (!me.el) {
90362             me.el = Ext.ShadowPool.pull();
90363             if (me.el.dom.nextSibling != target.dom) {
90364                 me.el.insertBefore(target);
90365             }
90366         }
90367         index = (parseInt(target.getStyle("z-index"), 10) - 1) || 0;
90368         me.el.setStyle("z-index", me.zIndex || index);
90369         if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
90370             me.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=" + me.opacity + ") progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (me.offset) + ")";
90371         }
90372         me.realign(
90373             target.getLeft(true),
90374             target.getTop(true),
90375             target.dom.offsetWidth,
90376             target.dom.offsetHeight
90377         );
90378         me.el.dom.style.display = "block";
90379     },
90380
90381     /**
90382      * Returns true if the shadow is visible, else false
90383      */
90384     isVisible: function() {
90385         return this.el ? true: false;
90386     },
90387
90388     /**
90389      * Direct alignment when values are already available. Show must be called at least once before
90390      * calling this method to ensure it is initialized.
90391      * @param {Number} left The target element left position
90392      * @param {Number} top The target element top position
90393      * @param {Number} width The target element width
90394      * @param {Number} height The target element height
90395      */
90396     realign: function(l, t, targetWidth, targetHeight) {
90397         if (!this.el) {
90398             return;
90399         }
90400         var adjusts = this.adjusts,
90401             d = this.el.dom,
90402             targetStyle = d.style,
90403             shadowWidth,
90404             shadowHeight,
90405             cn,
90406             sww, 
90407             sws, 
90408             shs;
90409
90410         targetStyle.left = (l + adjusts.l) + "px";
90411         targetStyle.top = (t + adjusts.t) + "px";
90412         shadowWidth = Math.max(targetWidth + adjusts.w, 0);
90413         shadowHeight = Math.max(targetHeight + adjusts.h, 0);
90414         sws = shadowWidth + "px";
90415         shs = shadowHeight + "px";
90416         if (targetStyle.width != sws || targetStyle.height != shs) {
90417             targetStyle.width = sws;
90418             targetStyle.height = shs;
90419             if (Ext.supports.CSS3BoxShadow) {
90420                 targetStyle.boxShadow = '0 0 ' + this.offset + 'px 0 #888';
90421             } else {
90422
90423                 // Adjust the 9 point framed element to poke out on the required sides
90424                 if (!Ext.isIE) {
90425                     cn = d.childNodes;
90426                     sww = Math.max(0, (shadowWidth - 12)) + "px";
90427                     cn[0].childNodes[1].style.width = sww;
90428                     cn[1].childNodes[1].style.width = sww;
90429                     cn[2].childNodes[1].style.width = sww;
90430                     cn[1].style.height = Math.max(0, (shadowHeight - 12)) + "px";
90431                 }
90432             }
90433         }
90434     },
90435
90436     /**
90437      * Hides this shadow
90438      */
90439     hide: function() {
90440         var me = this;
90441         
90442         if (me.el) {
90443             me.el.dom.style.display = "none";
90444             Ext.ShadowPool.push(me.el);
90445             delete me.el;
90446         }
90447     },
90448
90449     /**
90450      * Adjust the z-index of this shadow
90451      * @param {Number} zindex The new z-index
90452      */
90453     setZIndex: function(z) {
90454         this.zIndex = z;
90455         if (this.el) {
90456             this.el.setStyle("z-index", z);
90457         }
90458     },
90459     
90460     /**
90461      * Sets the opacity of the shadow
90462      * @param {Number} opacity The opacity
90463      */
90464     setOpacity: function(opacity){
90465         if (this.el) {
90466             if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
90467                 opacity = Math.floor(opacity * 100 / 2) / 100;
90468             }
90469             this.opacity = opacity;
90470             this.el.setOpacity(opacity);
90471         }
90472     }
90473 });
90474 /**
90475  * A split button that provides a built-in dropdown arrow that can fire an event separately from the default click event
90476  * of the button. Typically this would be used to display a dropdown menu that provides additional options to the
90477  * primary button action, but any custom handler can provide the arrowclick implementation.  Example usage:
90478  *
90479  *     @example
90480  *     // display a dropdown menu:
90481  *     Ext.create('Ext.button.Split', {
90482  *         renderTo: Ext.getBody(),
90483  *         text: 'Options',
90484  *         // handle a click on the button itself
90485  *         handler: function() {
90486  *             alert("The button was clicked");
90487  *         },
90488  *         menu: new Ext.menu.Menu({
90489  *             items: [
90490  *                 // these will render as dropdown menu items when the arrow is clicked:
90491  *                 {text: 'Item 1', handler: function(){ alert("Item 1 clicked"); }},
90492  *                 {text: 'Item 2', handler: function(){ alert("Item 2 clicked"); }}
90493  *             ]
90494  *         })
90495  *     });
90496  *
90497  * Instead of showing a menu, you can provide any type of custom functionality you want when the dropdown
90498  * arrow is clicked:
90499  *
90500  *     Ext.create('Ext.button.Split', {
90501  *         renderTo: 'button-ct',
90502  *         text: 'Options',
90503  *         handler: optionsHandler,
90504  *         arrowHandler: myCustomHandler
90505  *     });
90506  *
90507  */
90508 Ext.define('Ext.button.Split', {
90509
90510     /* Begin Definitions */
90511     alias: 'widget.splitbutton',
90512
90513     extend: 'Ext.button.Button',
90514     alternateClassName: 'Ext.SplitButton',
90515     /* End Definitions */
90516     
90517     /**
90518      * @cfg {Function} arrowHandler
90519      * A function called when the arrow button is clicked (can be used instead of click event)
90520      */
90521     /**
90522      * @cfg {String} arrowTooltip
90523      * The title attribute of the arrow
90524      */
90525
90526     // private
90527     arrowCls      : 'split',
90528     split         : true,
90529
90530     // private
90531     initComponent : function(){
90532         this.callParent();
90533         /**
90534          * @event arrowclick
90535          * Fires when this button's arrow is clicked.
90536          * @param {Ext.button.Split} this
90537          * @param {Event} e The click event
90538          */
90539         this.addEvents("arrowclick");
90540     },
90541
90542     /**
90543      * Sets this button's arrow click handler.
90544      * @param {Function} handler The function to call when the arrow is clicked
90545      * @param {Object} scope (optional) Scope for the function passed above
90546      */
90547     setArrowHandler : function(handler, scope){
90548         this.arrowHandler = handler;
90549         this.scope = scope;
90550     },
90551
90552     // private
90553     onClick : function(e, t) {
90554         var me = this;
90555         
90556         e.preventDefault();
90557         if (!me.disabled) {
90558             if (me.overMenuTrigger) {
90559                 me.maybeShowMenu();
90560                 me.fireEvent("arrowclick", me, e);
90561                 if (me.arrowHandler) {
90562                     me.arrowHandler.call(me.scope || me, me, e);
90563                 }
90564             } else {
90565                 me.doToggle();
90566                 me.fireHandler();
90567             }
90568         }
90569     }
90570 });
90571 /**
90572  * A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements. The button automatically
90573  * cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
90574  * {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
90575  * button displays the dropdown menu just like a normal SplitButton.  Example usage:
90576  *
90577  *     @example
90578  *     Ext.create('Ext.button.Cycle', {
90579  *         showText: true,
90580  *         prependText: 'View as ',
90581  *         renderTo: Ext.getBody(),
90582  *         menu: {
90583  *             id: 'view-type-menu',
90584  *             items: [{
90585  *                 text: 'text only',
90586  *                 iconCls: 'view-text',
90587  *                 checked: true
90588  *             },{
90589  *                 text: 'HTML',
90590  *                 iconCls: 'view-html'
90591  *             }]
90592  *         },
90593  *         changeHandler: function(cycleBtn, activeItem) {
90594  *             Ext.Msg.alert('Change View', activeItem.text);
90595  *         }
90596  *     });
90597  */
90598 Ext.define('Ext.button.Cycle', {
90599
90600     /* Begin Definitions */
90601
90602     alias: 'widget.cycle',
90603
90604     extend: 'Ext.button.Split',
90605     alternateClassName: 'Ext.CycleButton',
90606
90607     /* End Definitions */
90608
90609     /**
90610      * @cfg {Object[]} items
90611      * An array of {@link Ext.menu.CheckItem} **config** objects to be used when creating the button's menu items (e.g.,
90612      * `{text:'Foo', iconCls:'foo-icon'}`)
90613      * 
90614      * @deprecated 4.0 Use the {@link #menu} config instead. All menu items will be created as
90615      * {@link Ext.menu.CheckItem CheckItems}.
90616      */
90617     /**
90618      * @cfg {Boolean} [showText=false]
90619      * True to display the active item's text as the button text. The Button will show its
90620      * configured {@link #text} if this config is omitted.
90621      */
90622     /**
90623      * @cfg {String} [prependText='']
90624      * A static string to prepend before the active item's text when displayed as the button's text (only applies when
90625      * showText = true).
90626      */
90627     /**
90628      * @cfg {Function} changeHandler
90629      * A callback function that will be invoked each time the active menu item in the button's menu has changed. If this
90630      * callback is not supplied, the SplitButton will instead fire the {@link #change} event on active item change. The
90631      * changeHandler function will be called with the following argument list: (SplitButton this, Ext.menu.CheckItem
90632      * item)
90633      */
90634     /**
90635      * @cfg {String} forceIcon
90636      * A css class which sets an image to be used as the static icon for this button. This icon will always be displayed
90637      * regardless of which item is selected in the dropdown list. This overrides the default behavior of changing the
90638      * button's icon to match the selected item's icon on change.
90639      */
90640     /**
90641      * @property {Ext.menu.Menu} menu
90642      * The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the
90643      * available choices.
90644      */
90645
90646     // private
90647     getButtonText: function(item) {
90648         var me = this,
90649             text = '';
90650
90651         if (item && me.showText === true) {
90652             if (me.prependText) {
90653                 text += me.prependText;
90654             }
90655             text += item.text;
90656             return text;
90657         }
90658         return me.text;
90659     },
90660
90661     /**
90662      * Sets the button's active menu item.
90663      * @param {Ext.menu.CheckItem} item The item to activate
90664      * @param {Boolean} [suppressEvent=false] True to prevent the button's change event from firing.
90665      */
90666     setActiveItem: function(item, suppressEvent) {
90667         var me = this;
90668
90669         if (!Ext.isObject(item)) {
90670             item = me.menu.getComponent(item);
90671         }
90672         if (item) {
90673             if (!me.rendered) {
90674                 me.text = me.getButtonText(item);
90675                 me.iconCls = item.iconCls;
90676             } else {
90677                 me.setText(me.getButtonText(item));
90678                 me.setIconCls(item.iconCls);
90679             }
90680             me.activeItem = item;
90681             if (!item.checked) {
90682                 item.setChecked(true, false);
90683             }
90684             if (me.forceIcon) {
90685                 me.setIconCls(me.forceIcon);
90686             }
90687             if (!suppressEvent) {
90688                 me.fireEvent('change', me, item);
90689             }
90690         }
90691     },
90692
90693     /**
90694      * Gets the currently active menu item.
90695      * @return {Ext.menu.CheckItem} The active item
90696      */
90697     getActiveItem: function() {
90698         return this.activeItem;
90699     },
90700
90701     // private
90702     initComponent: function() {
90703         var me = this,
90704             checked = 0,
90705             items;
90706
90707         me.addEvents(
90708             /**
90709              * @event change
90710              * Fires after the button's active menu item has changed. Note that if a {@link #changeHandler} function is
90711              * set on this CycleButton, it will be called instead on active item change and this change event will not
90712              * be fired.
90713              * @param {Ext.button.Cycle} this
90714              * @param {Ext.menu.CheckItem} item The menu item that was selected
90715              */
90716             "change"
90717         );
90718
90719         if (me.changeHandler) {
90720             me.on('change', me.changeHandler, me.scope || me);
90721             delete me.changeHandler;
90722         }
90723
90724         // Allow them to specify a menu config which is a standard Button config.
90725         // Remove direct use of "items" in 5.0.
90726         items = (me.menu.items||[]).concat(me.items||[]);
90727         me.menu = Ext.applyIf({
90728             cls: Ext.baseCSSPrefix + 'cycle-menu',
90729             items: []
90730         }, me.menu);
90731
90732         // Convert all items to CheckItems
90733         Ext.each(items, function(item, i) {
90734             item = Ext.applyIf({
90735                 group: me.id,
90736                 itemIndex: i,
90737                 checkHandler: me.checkHandler,
90738                 scope: me,
90739                 checked: item.checked || false
90740             }, item);
90741             me.menu.items.push(item);
90742             if (item.checked) {
90743                 checked = i;
90744             }
90745         });
90746         me.itemCount = me.menu.items.length;
90747         me.callParent(arguments);
90748         me.on('click', me.toggleSelected, me);
90749         me.setActiveItem(checked, me);
90750
90751         // If configured with a fixed width, the cycling will center a different child item's text each click. Prevent this.
90752         if (me.width && me.showText) {
90753             me.addCls(Ext.baseCSSPrefix + 'cycle-fixed-width');
90754         }
90755     },
90756
90757     // private
90758     checkHandler: function(item, pressed) {
90759         if (pressed) {
90760             this.setActiveItem(item);
90761         }
90762     },
90763
90764     /**
90765      * This is normally called internally on button click, but can be called externally to advance the button's active
90766      * item programmatically to the next one in the menu. If the current item is the last one in the menu the active
90767      * item will be set to the first item in the menu.
90768      */
90769     toggleSelected: function() {
90770         var me = this,
90771             m = me.menu,
90772             checkItem;
90773
90774         checkItem = me.activeItem.next(':not([disabled])') || m.items.getAt(0);
90775         checkItem.setChecked(true);
90776     }
90777 });
90778 /**
90779  * Provides a container for arranging a group of related Buttons in a tabular manner.
90780  *
90781  *     @example
90782  *     Ext.create('Ext.panel.Panel', {
90783  *         title: 'Panel with ButtonGroup',
90784  *         width: 300,
90785  *         height:200,
90786  *         renderTo: document.body,
90787  *         bodyPadding: 10,
90788  *         html: 'HTML Panel Content',
90789  *         tbar: [{
90790  *             xtype: 'buttongroup',
90791  *             columns: 3,
90792  *             title: 'Clipboard',
90793  *             items: [{
90794  *                 text: 'Paste',
90795  *                 scale: 'large',
90796  *                 rowspan: 3,
90797  *                 iconCls: 'add',
90798  *                 iconAlign: 'top',
90799  *                 cls: 'btn-as-arrow'
90800  *             },{
90801  *                 xtype:'splitbutton',
90802  *                 text: 'Menu Button',
90803  *                 scale: 'large',
90804  *                 rowspan: 3,
90805  *                 iconCls: 'add',
90806  *                 iconAlign: 'top',
90807  *                 arrowAlign:'bottom',
90808  *                 menu: [{ text: 'Menu Item 1' }]
90809  *             },{
90810  *                 xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
90811  *             },{
90812  *                 text: 'Copy', iconCls: 'add16'
90813  *             },{
90814  *                 text: 'Format', iconCls: 'add16'
90815  *             }]
90816  *         }]
90817  *     });
90818  *
90819  */
90820 Ext.define('Ext.container.ButtonGroup', {
90821     extend: 'Ext.panel.Panel',
90822     alias: 'widget.buttongroup',
90823     alternateClassName: 'Ext.ButtonGroup',
90824
90825     /**
90826      * @cfg {Number} columns The `columns` configuration property passed to the
90827      * {@link #layout configured layout manager}. See {@link Ext.layout.container.Table#columns}.
90828      */
90829
90830     /**
90831      * @cfg {String} baseCls  Defaults to <tt>'x-btn-group'</tt>.  See {@link Ext.panel.Panel#baseCls}.
90832      */
90833     baseCls: Ext.baseCSSPrefix + 'btn-group',
90834
90835     /**
90836      * @cfg {Object} layout  Defaults to <tt>'table'</tt>.  See {@link Ext.container.Container#layout}.
90837      */
90838     layout: {
90839         type: 'table'
90840     },
90841
90842     defaultType: 'button',
90843
90844     /**
90845      * @cfg {Boolean} frame  Defaults to <tt>true</tt>.  See {@link Ext.panel.Panel#frame}.
90846      */
90847     frame: true,
90848
90849     frameHeader: false,
90850
90851     internalDefaults: {removeMode: 'container', hideParent: true},
90852
90853     initComponent : function(){
90854         // Copy the component's columns config to the layout if specified
90855         var me = this,
90856             cols = me.columns;
90857
90858         me.noTitleCls = me.baseCls + '-notitle';
90859         if (cols) {
90860             me.layout = Ext.apply({}, {columns: cols}, me.layout);
90861         }
90862
90863         if (!me.title) {
90864             me.addCls(me.noTitleCls);
90865         }
90866         me.callParent(arguments);
90867     },
90868
90869     afterLayout: function() {
90870         var me = this;
90871
90872         me.callParent(arguments);
90873
90874         // Pugly hack for a pugly browser:
90875         // If not an explicitly set width, then size the width to match the inner table
90876         if (me.layout.table && (Ext.isIEQuirks || Ext.isIE6) && !me.width) {
90877             var t = me.getTargetEl();
90878             t.setWidth(me.layout.table.offsetWidth + t.getPadding('lr'));
90879         }
90880
90881         // IE7 needs a forced repaint to make the top framing div expand to full width
90882         if (Ext.isIE7) {
90883             me.el.repaint();
90884         }
90885     },
90886
90887     afterRender: function() {
90888         var me = this;
90889
90890         //we need to add an addition item in here so the ButtonGroup title is centered
90891         if (me.header) {
90892             // Header text cannot flex, but must be natural size if it's being centered
90893             delete me.header.items.items[0].flex;
90894
90895             // For Centering, surround the text with two flex:1 spacers.
90896             me.suspendLayout = true;
90897             me.header.insert(1, {
90898                 xtype: 'component',
90899                 ui   : me.ui,
90900                 flex : 1
90901             });
90902             me.header.insert(0, {
90903                 xtype: 'component',
90904                 ui   : me.ui,
90905                 flex : 1
90906             });
90907             me.suspendLayout = false;
90908         }
90909
90910         me.callParent(arguments);
90911     },
90912
90913     // private
90914     onBeforeAdd: function(component) {
90915         if (component.is('button')) {
90916             component.ui = component.ui + '-toolbar';
90917         }
90918         this.callParent(arguments);
90919     },
90920
90921     //private
90922     applyDefaults: function(c) {
90923         if (!Ext.isString(c)) {
90924             c = this.callParent(arguments);
90925             var d = this.internalDefaults;
90926             if (c.events) {
90927                 Ext.applyIf(c.initialConfig, d);
90928                 Ext.apply(c, d);
90929             } else {
90930                 Ext.applyIf(c, d);
90931             }
90932         }
90933         return c;
90934     }
90935
90936     /**
90937      * @cfg {Array} tools  @hide
90938      */
90939     /**
90940      * @cfg {Boolean} collapsible  @hide
90941      */
90942     /**
90943      * @cfg {Boolean} collapseMode  @hide
90944      */
90945     /**
90946      * @cfg {Boolean} animCollapse  @hide
90947      */
90948     /**
90949      * @cfg {Boolean} closable  @hide
90950      */
90951 });
90952
90953 /**
90954  * A specialized container representing the viewable application area (the browser viewport).
90955  *
90956  * The Viewport renders itself to the document body, and automatically sizes itself to the size of
90957  * the browser viewport and manages window resizing. There may only be one Viewport created
90958  * in a page.
90959  *
90960  * Like any {@link Ext.container.Container Container}, a Viewport will only perform sizing and positioning
90961  * on its child Components if you configure it with a {@link #layout}.
90962  *
90963  * A Common layout used with Viewports is {@link Ext.layout.container.Border border layout}, but if the
90964  * required layout is simpler, a different layout should be chosen.
90965  *
90966  * For example, to simply make a single child item occupy all available space, use
90967  * {@link Ext.layout.container.Fit fit layout}.
90968  *
90969  * To display one "active" item at full size from a choice of several child items, use
90970  * {@link Ext.layout.container.Card card layout}.
90971  *
90972  * Inner layouts are available by virtue of the fact that all {@link Ext.panel.Panel Panel}s
90973  * added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
90974  * method of any of its child Panels may themselves have a layout.
90975  *
90976  * The Viewport does not provide scrolling, so child Panels within the Viewport should provide
90977  * for scrolling if needed using the {@link #autoScroll} config.
90978  *
90979  * An example showing a classic application border layout:
90980  *
90981  *     @example
90982  *     Ext.create('Ext.container.Viewport', {
90983  *         layout: 'border',
90984  *         items: [{
90985  *             region: 'north',
90986  *             html: '<h1 class="x-panel-header">Page Title</h1>',
90987  *             autoHeight: true,
90988  *             border: false,
90989  *             margins: '0 0 5 0'
90990  *         }, {
90991  *             region: 'west',
90992  *             collapsible: true,
90993  *             title: 'Navigation',
90994  *             width: 150
90995  *             // could use a TreePanel or AccordionLayout for navigational items
90996  *         }, {
90997  *             region: 'south',
90998  *             title: 'South Panel',
90999  *             collapsible: true,
91000  *             html: 'Information goes here',
91001  *             split: true,
91002  *             height: 100,
91003  *             minHeight: 100
91004  *         }, {
91005  *             region: 'east',
91006  *             title: 'East Panel',
91007  *             collapsible: true,
91008  *             split: true,
91009  *             width: 150
91010  *         }, {
91011  *             region: 'center',
91012  *             xtype: 'tabpanel', // TabPanel itself has no title
91013  *             activeTab: 0,      // First tab active by default
91014  *             items: {
91015  *                 title: 'Default Tab',
91016  *                 html: 'The first tab\'s content. Others may be added dynamically'
91017  *             }
91018  *         }]
91019  *     });
91020  */
91021 Ext.define('Ext.container.Viewport', {
91022     extend: 'Ext.container.Container',
91023     alias: 'widget.viewport',
91024     requires: ['Ext.EventManager'],
91025     alternateClassName: 'Ext.Viewport',
91026
91027     // Privatize config options which, if used, would interfere with the
91028     // correct operation of the Viewport as the sole manager of the
91029     // layout of the document body.
91030
91031     /**
91032      * @cfg {String/HTMLElement/Ext.Element} applyTo
91033      * Not applicable.
91034      */
91035
91036     /**
91037      * @cfg {Boolean} allowDomMove
91038      * Not applicable.
91039      */
91040
91041     /**
91042      * @cfg {Boolean} hideParent
91043      * Not applicable.
91044      */
91045
91046     /**
91047      * @cfg {String/HTMLElement/Ext.Element} renderTo
91048      * Not applicable. Always renders to document body.
91049      */
91050
91051     /**
91052      * @cfg {Boolean} hideParent
91053      * Not applicable.
91054      */
91055
91056     /**
91057      * @cfg {Number} height
91058      * Not applicable. Sets itself to viewport width.
91059      */
91060
91061     /**
91062      * @cfg {Number} width
91063      * Not applicable. Sets itself to viewport height.
91064      */
91065
91066     /**
91067      * @cfg {Boolean} autoHeight
91068      * Not applicable.
91069      */
91070
91071     /**
91072      * @cfg {Boolean} autoWidth
91073      * Not applicable.
91074      */
91075
91076     /**
91077      * @cfg {Boolean} deferHeight
91078      * Not applicable.
91079      */
91080
91081     /**
91082      * @cfg {Boolean} monitorResize
91083      * Not applicable.
91084      */
91085
91086     isViewport: true,
91087
91088     ariaRole: 'application',
91089
91090     initComponent : function() {
91091         var me = this,
91092             html = Ext.fly(document.body.parentNode),
91093             el;
91094         me.callParent(arguments);
91095         html.addCls(Ext.baseCSSPrefix + 'viewport');
91096         if (me.autoScroll) {
91097             html.setStyle('overflow', 'auto');
91098         }
91099         me.el = el = Ext.getBody();
91100         el.setHeight = Ext.emptyFn;
91101         el.setWidth = Ext.emptyFn;
91102         el.setSize = Ext.emptyFn;
91103         el.dom.scroll = 'no';
91104         me.allowDomMove = false;
91105         Ext.EventManager.onWindowResize(me.fireResize, me);
91106         me.renderTo = me.el;
91107         me.width = Ext.Element.getViewportWidth();
91108         me.height = Ext.Element.getViewportHeight();
91109     },
91110
91111     fireResize : function(w, h){
91112         // setSize is the single entry point to layouts
91113         this.setSize(w, h);
91114     }
91115 });
91116
91117 /*
91118  * This is a derivative of the similarly named class in the YUI Library.
91119  * The original license:
91120  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
91121  * Code licensed under the BSD License:
91122  * http://developer.yahoo.net/yui/license.txt
91123  */
91124
91125
91126 /**
91127  * @class Ext.dd.DDTarget
91128  * @extends Ext.dd.DragDrop
91129  * A DragDrop implementation that does not move, but can be a drop
91130  * target.  You would get the same result by simply omitting implementation
91131  * for the event callbacks, but this way we reduce the processing cost of the
91132  * event listener and the callbacks.
91133  */
91134 Ext.define('Ext.dd.DDTarget', {
91135     extend: 'Ext.dd.DragDrop',
91136
91137     /**
91138      * Creates new DDTarget.
91139      * @param {String} id the id of the element that is a drop target
91140      * @param {String} sGroup the group of related DragDrop objects
91141      * @param {Object} config an object containing configurable attributes.
91142      * Valid properties for DDTarget in addition to those in DragDrop: none.
91143      */
91144     constructor: function(id, sGroup, config) {
91145         if (id) {
91146             this.initTarget(id, sGroup, config);
91147         }
91148     },
91149
91150     /**
91151      * @hide
91152      * Overridden and disabled. A DDTarget does not support being dragged.
91153      * @method
91154      */
91155     getDragEl: Ext.emptyFn,
91156     /**
91157      * @hide
91158      * Overridden and disabled. A DDTarget does not support being dragged.
91159      * @method
91160      */
91161     isValidHandleChild: Ext.emptyFn,
91162     /**
91163      * @hide
91164      * Overridden and disabled. A DDTarget does not support being dragged.
91165      * @method
91166      */
91167     startDrag: Ext.emptyFn,
91168     /**
91169      * @hide
91170      * Overridden and disabled. A DDTarget does not support being dragged.
91171      * @method
91172      */
91173     endDrag: Ext.emptyFn,
91174     /**
91175      * @hide
91176      * Overridden and disabled. A DDTarget does not support being dragged.
91177      * @method
91178      */
91179     onDrag: Ext.emptyFn,
91180     /**
91181      * @hide
91182      * Overridden and disabled. A DDTarget does not support being dragged.
91183      * @method
91184      */
91185     onDragDrop: Ext.emptyFn,
91186     /**
91187      * @hide
91188      * Overridden and disabled. A DDTarget does not support being dragged.
91189      * @method
91190      */
91191     onDragEnter: Ext.emptyFn,
91192     /**
91193      * @hide
91194      * Overridden and disabled. A DDTarget does not support being dragged.
91195      * @method
91196      */
91197     onDragOut: Ext.emptyFn,
91198     /**
91199      * @hide
91200      * Overridden and disabled. A DDTarget does not support being dragged.
91201      * @method
91202      */
91203     onDragOver: Ext.emptyFn,
91204     /**
91205      * @hide
91206      * Overridden and disabled. A DDTarget does not support being dragged.
91207      * @method
91208      */
91209     onInvalidDrop: Ext.emptyFn,
91210     /**
91211      * @hide
91212      * Overridden and disabled. A DDTarget does not support being dragged.
91213      * @method
91214      */
91215     onMouseDown: Ext.emptyFn,
91216     /**
91217      * @hide
91218      * Overridden and disabled. A DDTarget does not support being dragged.
91219      * @method
91220      */
91221     onMouseUp: Ext.emptyFn,
91222     /**
91223      * @hide
91224      * Overridden and disabled. A DDTarget does not support being dragged.
91225      * @method
91226      */
91227     setXConstraint: Ext.emptyFn,
91228     /**
91229      * @hide
91230      * Overridden and disabled. A DDTarget does not support being dragged.
91231      * @method
91232      */
91233     setYConstraint: Ext.emptyFn,
91234     /**
91235      * @hide
91236      * Overridden and disabled. A DDTarget does not support being dragged.
91237      * @method
91238      */
91239     resetConstraints: Ext.emptyFn,
91240     /**
91241      * @hide
91242      * Overridden and disabled. A DDTarget does not support being dragged.
91243      * @method
91244      */
91245     clearConstraints: Ext.emptyFn,
91246     /**
91247      * @hide
91248      * Overridden and disabled. A DDTarget does not support being dragged.
91249      * @method
91250      */
91251     clearTicks: Ext.emptyFn,
91252     /**
91253      * @hide
91254      * Overridden and disabled. A DDTarget does not support being dragged.
91255      * @method
91256      */
91257     setInitPosition: Ext.emptyFn,
91258     /**
91259      * @hide
91260      * Overridden and disabled. A DDTarget does not support being dragged.
91261      * @method
91262      */
91263     setDragElId: Ext.emptyFn,
91264     /**
91265      * @hide
91266      * Overridden and disabled. A DDTarget does not support being dragged.
91267      * @method
91268      */
91269     setHandleElId: Ext.emptyFn,
91270     /**
91271      * @hide
91272      * Overridden and disabled. A DDTarget does not support being dragged.
91273      * @method
91274      */
91275     setOuterHandleElId: Ext.emptyFn,
91276     /**
91277      * @hide
91278      * Overridden and disabled. A DDTarget does not support being dragged.
91279      * @method
91280      */
91281     addInvalidHandleClass: Ext.emptyFn,
91282     /**
91283      * @hide
91284      * Overridden and disabled. A DDTarget does not support being dragged.
91285      * @method
91286      */
91287     addInvalidHandleId: Ext.emptyFn,
91288     /**
91289      * @hide
91290      * Overridden and disabled. A DDTarget does not support being dragged.
91291      * @method
91292      */
91293     addInvalidHandleType: Ext.emptyFn,
91294     /**
91295      * @hide
91296      * Overridden and disabled. A DDTarget does not support being dragged.
91297      * @method
91298      */
91299     removeInvalidHandleClass: Ext.emptyFn,
91300     /**
91301      * @hide
91302      * Overridden and disabled. A DDTarget does not support being dragged.
91303      * @method
91304      */
91305     removeInvalidHandleId: Ext.emptyFn,
91306     /**
91307      * @hide
91308      * Overridden and disabled. A DDTarget does not support being dragged.
91309      * @method
91310      */
91311     removeInvalidHandleType: Ext.emptyFn,
91312
91313     toString: function() {
91314         return ("DDTarget " + this.id);
91315     }
91316 });
91317 /**
91318  * @class Ext.dd.DragTracker
91319  * A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
91320  * as well as during the drag. This is useful for components such as {@link Ext.slider.Multi}, where there is
91321  * an element that can be dragged around to change the Slider's value.
91322  * DragTracker provides a series of template methods that should be overridden to provide functionality
91323  * in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
91324  * See {@link Ext.slider.Multi}'s initEvents function for an example implementation.
91325  */
91326 Ext.define('Ext.dd.DragTracker', {
91327
91328     uses: ['Ext.util.Region'],
91329
91330     mixins: {
91331         observable: 'Ext.util.Observable'
91332     },
91333
91334     /**
91335      * @property {Boolean} active
91336      * Read-only property indicated whether the user is currently dragging this
91337      * tracker.
91338      */
91339     active: false,
91340
91341     /**
91342      * @property {HTMLElement} dragTarget
91343      * <p><b>Only valid during drag operations. Read-only.</b></p>
91344      * <p>The element being dragged.</p>
91345      * <p>If the {@link #delegate} option is used, this will be the delegate element which was mousedowned.</p>
91346      */
91347
91348     /**
91349      * @cfg {Boolean} trackOver
91350      * <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>
91351      * <p>This is implicitly set when an {@link #overCls} is specified.</p>
91352      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
91353      */
91354     trackOver: false,
91355
91356     /**
91357      * @cfg {String} overCls
91358      * <p>A CSS class to add to the DragTracker's target element when the element (or, if the {@link #delegate} option is used,
91359      * when a delegate element) is mouseovered.</p>
91360      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
91361      */
91362
91363     /**
91364      * @cfg {Ext.util.Region/Ext.Element} constrainTo
91365      * <p>A {@link Ext.util.Region Region} (Or an element from which a Region measurement will be read) which is used to constrain
91366      * the result of the {@link #getOffset} call.</p>
91367      * <p>This may be set any time during the DragTracker's lifecycle to set a dynamic constraining region.</p>
91368      */
91369
91370     /**
91371      * @cfg {Number} tolerance
91372      * Number of pixels the drag target must be moved before dragging is
91373      * considered to have started. Defaults to <code>5</code>.
91374      */
91375     tolerance: 5,
91376
91377     /**
91378      * @cfg {Boolean/Number} autoStart
91379      * Defaults to <code>false</code>. Specify <code>true</code> to defer trigger start by 1000 ms.
91380      * Specify a Number for the number of milliseconds to defer trigger start.
91381      */
91382     autoStart: false,
91383
91384     /**
91385      * @cfg {String} delegate
91386      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the DragTracker's encapsulating
91387      * Element which are the tracked elements. This limits tracking to only begin when the matching elements are mousedowned.</p>
91388      * <p>This may also be a specific child element within the DragTracker's encapsulating element to use as the tracked element.</p>
91389      */
91390
91391     /**
91392      * @cfg {Boolean} preventDefault
91393      * Specify <code>false</code> to enable default actions on onMouseDown events. Defaults to <code>true</code>.
91394      */
91395
91396     /**
91397      * @cfg {Boolean} stopEvent
91398      * 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>.
91399      */
91400
91401     constructor : function(config){
91402         Ext.apply(this, config);
91403         this.addEvents(
91404             /**
91405              * @event mouseover <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
91406              * <p>Fires when the mouse enters the DragTracker's target element (or if {@link #delegate} is
91407              * used, when the mouse enters a delegate element).</p>
91408              * @param {Object} this
91409              * @param {Object} e event object
91410              * @param {HTMLElement} target The element mouseovered.
91411              */
91412             'mouseover',
91413
91414             /**
91415              * @event mouseout <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
91416              * <p>Fires when the mouse exits the DragTracker's target element (or if {@link #delegate} is
91417              * used, when the mouse exits a delegate element).</p>
91418              * @param {Object} this
91419              * @param {Object} e event object
91420              */
91421             'mouseout',
91422
91423             /**
91424              * @event mousedown <p>Fires when the mouse button is pressed down, but before a drag operation begins. The
91425              * drag operation begins after either the mouse has been moved by {@link #tolerance} pixels, or after
91426              * the {@link #autoStart} timer fires.</p>
91427              * <p>Return false to veto the drag operation.</p>
91428              * @param {Object} this
91429              * @param {Object} e event object
91430              */
91431             'mousedown',
91432
91433             /**
91434              * @event mouseup
91435              * @param {Object} this
91436              * @param {Object} e event object
91437              */
91438             'mouseup',
91439
91440             /**
91441              * @event mousemove Fired when the mouse is moved. Returning false cancels the drag operation.
91442              * @param {Object} this
91443              * @param {Object} e event object
91444              */
91445             'mousemove',
91446
91447             /**
91448              * @event beforestart
91449              * @param {Object} this
91450              * @param {Object} e event object
91451              */
91452             'beforedragstart',
91453
91454             /**
91455              * @event dragstart
91456              * @param {Object} this
91457              * @param {Object} e event object
91458              */
91459             'dragstart',
91460
91461             /**
91462              * @event dragend
91463              * @param {Object} this
91464              * @param {Object} e event object
91465              */
91466             'dragend',
91467
91468             /**
91469              * @event drag
91470              * @param {Object} this
91471              * @param {Object} e event object
91472              */
91473             'drag'
91474         );
91475
91476         this.dragRegion = Ext.create('Ext.util.Region', 0,0,0,0);
91477
91478         if (this.el) {
91479             this.initEl(this.el);
91480         }
91481
91482         // Dont pass the config so that it is not applied to 'this' again
91483         this.mixins.observable.constructor.call(this);
91484         if (this.disabled) {
91485             this.disable();
91486         }
91487
91488     },
91489
91490     /**
91491      * Initializes the DragTracker on a given element.
91492      * @param {Ext.Element/HTMLElement} el The element
91493      */
91494     initEl: function(el) {
91495         this.el = Ext.get(el);
91496
91497         // The delegate option may also be an element on which to listen
91498         this.handle = Ext.get(this.delegate);
91499
91500         // If delegate specified an actual element to listen on, we do not use the delegate listener option
91501         this.delegate = this.handle ? undefined : this.delegate;
91502
91503         if (!this.handle) {
91504             this.handle = this.el;
91505         }
91506
91507         // Add a mousedown listener which reacts only on the elements targeted by the delegate config.
91508         // We process mousedown to begin tracking.
91509         this.mon(this.handle, {
91510             mousedown: this.onMouseDown,
91511             delegate: this.delegate,
91512             scope: this
91513         });
91514
91515         // If configured to do so, track mouse entry and exit into the target (or delegate).
91516         // The mouseover and mouseout CANNOT be replaced with mouseenter and mouseleave
91517         // because delegate cannot work with those pseudoevents. Entry/exit checking is done in the handler.
91518         if (this.trackOver || this.overCls) {
91519             this.mon(this.handle, {
91520                 mouseover: this.onMouseOver,
91521                 mouseout: this.onMouseOut,
91522                 delegate: this.delegate,
91523                 scope: this
91524             });
91525         }
91526     },
91527
91528     disable: function() {
91529         this.disabled = true;
91530     },
91531
91532     enable: function() {
91533         this.disabled = false;
91534     },
91535
91536     destroy : function() {
91537         this.clearListeners();
91538         delete this.el;
91539     },
91540
91541     // When the pointer enters a tracking element, fire a mouseover if the mouse entered from outside.
91542     // This is mouseenter functionality, but we cannot use mouseenter because we are using "delegate" to filter mouse targets
91543     onMouseOver: function(e, target) {
91544         var me = this;
91545         if (!me.disabled) {
91546             if (Ext.EventManager.contains(e) || me.delegate) {
91547                 me.mouseIsOut = false;
91548                 if (me.overCls) {
91549                     me.el.addCls(me.overCls);
91550                 }
91551                 me.fireEvent('mouseover', me, e, me.delegate ? e.getTarget(me.delegate, target) : me.handle);
91552             }
91553         }
91554     },
91555
91556     // When the pointer exits a tracking element, fire a mouseout.
91557     // This is mouseleave functionality, but we cannot use mouseleave because we are using "delegate" to filter mouse targets
91558     onMouseOut: function(e) {
91559         if (this.mouseIsDown) {
91560             this.mouseIsOut = true;
91561         } else {
91562             if (this.overCls) {
91563                 this.el.removeCls(this.overCls);
91564             }
91565             this.fireEvent('mouseout', this, e);
91566         }
91567     },
91568
91569     onMouseDown: function(e, target){
91570         // If this is disabled, or the mousedown has been processed by an upstream DragTracker, return
91571         if (this.disabled ||e.dragTracked) {
91572             return;
91573         }
91574
91575         // This information should be available in mousedown listener and onBeforeStart implementations
91576         this.dragTarget = this.delegate ? target : this.handle.dom;
91577         this.startXY = this.lastXY = e.getXY();
91578         this.startRegion = Ext.fly(this.dragTarget).getRegion();
91579
91580         if (this.fireEvent('mousedown', this, e) === false ||
91581             this.fireEvent('beforedragstart', this, e) === false ||
91582             this.onBeforeStart(e) === false) {
91583             return;
91584         }
91585
91586         // Track when the mouse is down so that mouseouts while the mouse is down are not processed.
91587         // The onMouseOut method will only ever be called after mouseup.
91588         this.mouseIsDown = true;
91589
91590         // Flag for downstream DragTracker instances that the mouse is being tracked.
91591         e.dragTracked = true;
91592
91593         if (this.preventDefault !== false) {
91594             e.preventDefault();
91595         }
91596         Ext.getDoc().on({
91597             scope: this,
91598             mouseup: this.onMouseUp,
91599             mousemove: this.onMouseMove,
91600             selectstart: this.stopSelect
91601         });
91602         if (this.autoStart) {
91603             this.timer =  Ext.defer(this.triggerStart, this.autoStart === true ? 1000 : this.autoStart, this, [e]);
91604         }
91605     },
91606
91607     onMouseMove: function(e, target){
91608         // BrowserBug: IE hack to see if button was released outside of window.
91609         // Needed in IE6-9 in quirks and strictmode
91610         if (this.active && Ext.isIE && !e.browserEvent.button) {
91611             e.preventDefault();
91612             this.onMouseUp(e);
91613             return;
91614         }
91615
91616         e.preventDefault();
91617         var xy = e.getXY(),
91618             s = this.startXY;
91619
91620         this.lastXY = xy;
91621         if (!this.active) {
91622             if (Math.max(Math.abs(s[0]-xy[0]), Math.abs(s[1]-xy[1])) > this.tolerance) {
91623                 this.triggerStart(e);
91624             } else {
91625                 return;
91626             }
91627         }
91628
91629         // Returning false from a mousemove listener deactivates
91630         if (this.fireEvent('mousemove', this, e) === false) {
91631             this.onMouseUp(e);
91632         } else {
91633             this.onDrag(e);
91634             this.fireEvent('drag', this, e);
91635         }
91636     },
91637
91638     onMouseUp: function(e) {
91639         // Clear the flag which ensures onMouseOut fires only after the mouse button
91640         // is lifted if the mouseout happens *during* a drag.
91641         this.mouseIsDown = false;
91642
91643         // If we mouseouted the el *during* the drag, the onMouseOut method will not have fired. Ensure that it gets processed.
91644         if (this.mouseIsOut) {
91645             this.mouseIsOut = false;
91646             this.onMouseOut(e);
91647         }
91648         e.preventDefault();
91649         this.fireEvent('mouseup', this, e);
91650         this.endDrag(e);
91651     },
91652
91653     /**
91654      * @private
91655      * Stop the drag operation, and remove active mouse listeners.
91656      */
91657     endDrag: function(e) {
91658         var doc = Ext.getDoc(),
91659         wasActive = this.active;
91660
91661         doc.un('mousemove', this.onMouseMove, this);
91662         doc.un('mouseup', this.onMouseUp, this);
91663         doc.un('selectstart', this.stopSelect, this);
91664         this.clearStart();
91665         this.active = false;
91666         if (wasActive) {
91667             this.onEnd(e);
91668             this.fireEvent('dragend', this, e);
91669         }
91670         // Private property calculated when first required and only cached during a drag
91671         delete this._constrainRegion;
91672
91673         // Remove flag from event singleton.  Using "Ext.EventObject" here since "endDrag" is called directly in some cases without an "e" param
91674         delete Ext.EventObject.dragTracked;
91675     },
91676
91677     triggerStart: function(e) {
91678         this.clearStart();
91679         this.active = true;
91680         this.onStart(e);
91681         this.fireEvent('dragstart', this, e);
91682     },
91683
91684     clearStart : function() {
91685         if (this.timer) {
91686             clearTimeout(this.timer);
91687             delete this.timer;
91688         }
91689     },
91690
91691     stopSelect : function(e) {
91692         e.stopEvent();
91693         return false;
91694     },
91695
91696     /**
91697      * Template method which should be overridden by each DragTracker instance. Called when the user first clicks and
91698      * holds the mouse button down. Return false to disallow the drag
91699      * @param {Ext.EventObject} e The event object
91700      * @template
91701      */
91702     onBeforeStart : function(e) {
91703
91704     },
91705
91706     /**
91707      * Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
91708      * (e.g. the user has moved the tracked element beyond the specified tolerance)
91709      * @param {Ext.EventObject} e The event object
91710      * @template
91711      */
91712     onStart : function(xy) {
91713
91714     },
91715
91716     /**
91717      * Template method which should be overridden by each DragTracker instance. Called whenever a drag has been detected.
91718      * @param {Ext.EventObject} e The event object
91719      * @template
91720      */
91721     onDrag : function(e) {
91722
91723     },
91724
91725     /**
91726      * Template method which should be overridden by each DragTracker instance. Called when a drag operation has been completed
91727      * (e.g. the user clicked and held the mouse down, dragged the element and then released the mouse button)
91728      * @param {Ext.EventObject} e The event object
91729      * @template
91730      */
91731     onEnd : function(e) {
91732
91733     },
91734
91735     /**
91736      * </p>Returns the drag target. This is usually the DragTracker's encapsulating element.</p>
91737      * <p>If the {@link #delegate} option is being used, this may be a child element which matches the
91738      * {@link #delegate} selector.</p>
91739      * @return {Ext.Element} The element currently being tracked.
91740      */
91741     getDragTarget : function(){
91742         return this.dragTarget;
91743     },
91744
91745     /**
91746      * @private
91747      * @returns {Ext.Element} The DragTracker's encapsulating element.
91748      */
91749     getDragCt : function(){
91750         return this.el;
91751     },
91752
91753     /**
91754      * @private
91755      * Return the Region into which the drag operation is constrained.
91756      * Either the XY pointer itself can be constrained, or the dragTarget element
91757      * The private property _constrainRegion is cached until onMouseUp
91758      */
91759     getConstrainRegion: function() {
91760         if (this.constrainTo) {
91761             if (this.constrainTo instanceof Ext.util.Region) {
91762                 return this.constrainTo;
91763             }
91764             if (!this._constrainRegion) {
91765                 this._constrainRegion = Ext.fly(this.constrainTo).getViewRegion();
91766             }
91767         } else {
91768             if (!this._constrainRegion) {
91769                 this._constrainRegion = this.getDragCt().getViewRegion();
91770             }
91771         }
91772         return this._constrainRegion;
91773     },
91774
91775     getXY : function(constrain){
91776         return constrain ? this.constrainModes[constrain](this, this.lastXY) : this.lastXY;
91777     },
91778
91779     /**
91780      * Returns the X, Y offset of the current mouse position from the mousedown point.
91781      *
91782      * This method may optionally constrain the real offset values, and returns a point coerced in one
91783      * of two modes:
91784      *
91785      *  - `point`
91786      *    The current mouse position is coerced into the constrainRegion and the resulting position is returned.
91787      *  - `dragTarget`
91788      *    The new {@link Ext.util.Region Region} of the {@link #getDragTarget dragTarget} is calculated
91789      *    based upon the current mouse position, and then coerced into the constrainRegion. The returned
91790      *    mouse position is then adjusted by the same delta as was used to coerce the region.\
91791      *
91792      * @param constrainMode {String} (Optional) If omitted the true mouse position is returned. May be passed
91793      * as `point` or `dragTarget`. See above.
91794      * @returns {Number[]} The `X, Y` offset from the mousedown point, optionally constrained.
91795      */
91796     getOffset : function(constrain){
91797         var xy = this.getXY(constrain),
91798             s = this.startXY;
91799
91800         return [xy[0]-s[0], xy[1]-s[1]];
91801     },
91802
91803     constrainModes: {
91804         // Constrain the passed point to within the constrain region
91805         point: function(me, xy) {
91806             var dr = me.dragRegion,
91807                 constrainTo = me.getConstrainRegion();
91808
91809             // No constraint
91810             if (!constrainTo) {
91811                 return xy;
91812             }
91813
91814             dr.x = dr.left = dr[0] = dr.right = xy[0];
91815             dr.y = dr.top = dr[1] = dr.bottom = xy[1];
91816             dr.constrainTo(constrainTo);
91817
91818             return [dr.left, dr.top];
91819         },
91820
91821         // Constrain the dragTarget to within the constrain region. Return the passed xy adjusted by the same delta.
91822         dragTarget: function(me, xy) {
91823             var s = me.startXY,
91824                 dr = me.startRegion.copy(),
91825                 constrainTo = me.getConstrainRegion(),
91826                 adjust;
91827
91828             // No constraint
91829             if (!constrainTo) {
91830                 return xy;
91831             }
91832
91833             // See where the passed XY would put the dragTarget if translated by the unconstrained offset.
91834             // If it overflows, we constrain the passed XY to bring the potential
91835             // region back within the boundary.
91836             dr.translateBy(xy[0]-s[0], xy[1]-s[1]);
91837
91838             // Constrain the X coordinate by however much the dragTarget overflows
91839             if (dr.right > constrainTo.right) {
91840                 xy[0] += adjust = (constrainTo.right - dr.right);    // overflowed the right
91841                 dr.left += adjust;
91842             }
91843             if (dr.left < constrainTo.left) {
91844                 xy[0] += (constrainTo.left - dr.left);      // overflowed the left
91845             }
91846
91847             // Constrain the Y coordinate by however much the dragTarget overflows
91848             if (dr.bottom > constrainTo.bottom) {
91849                 xy[1] += adjust = (constrainTo.bottom - dr.bottom);  // overflowed the bottom
91850                 dr.top += adjust;
91851             }
91852             if (dr.top < constrainTo.top) {
91853                 xy[1] += (constrainTo.top - dr.top);        // overflowed the top
91854             }
91855             return xy;
91856         }
91857     }
91858 });
91859 /**
91860  * @class Ext.dd.DragZone
91861  * @extends Ext.dd.DragSource
91862  * <p>This class provides a container DD instance that allows dragging of multiple child source nodes.</p>
91863  * <p>This class does not move the drag target nodes, but a proxy element which may contain
91864  * any DOM structure you wish. The DOM element to show in the proxy is provided by either a
91865  * provided implementation of {@link #getDragData}, or by registered draggables registered with {@link Ext.dd.Registry}</p>
91866  * <p>If you wish to provide draggability for an arbitrary number of DOM nodes, each of which represent some
91867  * application object (For example nodes in a {@link Ext.view.View DataView}) then use of this class
91868  * is the most efficient way to "activate" those nodes.</p>
91869  * <p>By default, this class requires that draggable child nodes are registered with {@link Ext.dd.Registry}.
91870  * However a simpler way to allow a DragZone to manage any number of draggable elements is to configure
91871  * the DragZone with  an implementation of the {@link #getDragData} method which interrogates the passed
91872  * mouse event to see if it has taken place within an element, or class of elements. This is easily done
91873  * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
91874  * {@link Ext.DomQuery} selector. For example, to make the nodes of a DataView draggable, use the following
91875  * technique. Knowledge of the use of the DataView is required:</p><pre><code>
91876 myDataView.on('render', function(v) {
91877     myDataView.dragZone = new Ext.dd.DragZone(v.getEl(), {
91878
91879 //      On receipt of a mousedown event, see if it is within a DataView node.
91880 //      Return a drag data object if so.
91881         getDragData: function(e) {
91882
91883 //          Use the DataView's own itemSelector (a mandatory property) to
91884 //          test if the mousedown is within one of the DataView's nodes.
91885             var sourceEl = e.getTarget(v.itemSelector, 10);
91886
91887 //          If the mousedown is within a DataView node, clone the node to produce
91888 //          a ddel element for use by the drag proxy. Also add application data
91889 //          to the returned data object.
91890             if (sourceEl) {
91891                 d = sourceEl.cloneNode(true);
91892                 d.id = Ext.id();
91893                 return {
91894                     ddel: d,
91895                     sourceEl: sourceEl,
91896                     repairXY: Ext.fly(sourceEl).getXY(),
91897                     sourceStore: v.store,
91898                     draggedRecord: v.{@link Ext.view.View#getRecord getRecord}(sourceEl)
91899                 }
91900             }
91901         },
91902
91903 //      Provide coordinates for the proxy to slide back to on failed drag.
91904 //      This is the original XY coordinates of the draggable element captured
91905 //      in the getDragData method.
91906         getRepairXY: function() {
91907             return this.dragData.repairXY;
91908         }
91909     });
91910 });</code></pre>
91911  * See the {@link Ext.dd.DropZone DropZone} documentation for details about building a DropZone which
91912  * cooperates with this DragZone.
91913  */
91914 Ext.define('Ext.dd.DragZone', {
91915
91916     extend: 'Ext.dd.DragSource',
91917
91918     /**
91919      * Creates new DragZone.
91920      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
91921      * @param {Object} config
91922      */
91923     constructor : function(el, config){
91924         this.callParent([el, config]);
91925         if (this.containerScroll) {
91926             Ext.dd.ScrollManager.register(this.el);
91927         }
91928     },
91929
91930     /**
91931      * This property contains the data representing the dragged object. This data is set up by the implementation
91932      * of the {@link #getDragData} method. It must contain a <tt>ddel</tt> property, but can contain
91933      * any other data according to the application's needs.
91934      * @type Object
91935      * @property dragData
91936      */
91937
91938     /**
91939      * @cfg {Boolean} containerScroll True to register this container with the Scrollmanager
91940      * for auto scrolling during drag operations.
91941      */
91942
91943     /**
91944      * Called when a mousedown occurs in this container. Looks in {@link Ext.dd.Registry}
91945      * for a valid target to drag based on the mouse down. Override this method
91946      * to provide your own lookup logic (e.g. finding a child by class name). Make sure your returned
91947      * object has a "ddel" attribute (with an HTML Element) for other functions to work.
91948      * @param {Event} e The mouse down event
91949      * @return {Object} The dragData
91950      */
91951     getDragData : function(e){
91952         return Ext.dd.Registry.getHandleFromEvent(e);
91953     },
91954
91955     /**
91956      * Called once drag threshold has been reached to initialize the proxy element. By default, it clones the
91957      * this.dragData.ddel
91958      * @param {Number} x The x position of the click on the dragged object
91959      * @param {Number} y The y position of the click on the dragged object
91960      * @return {Boolean} true to continue the drag, false to cancel
91961      */
91962     onInitDrag : function(x, y){
91963         this.proxy.update(this.dragData.ddel.cloneNode(true));
91964         this.onStartDrag(x, y);
91965         return true;
91966     },
91967
91968     /**
91969      * Called after a repair of an invalid drop. By default, highlights this.dragData.ddel
91970      */
91971     afterRepair : function(){
91972         var me = this;
91973         if (Ext.enableFx) {
91974             Ext.fly(me.dragData.ddel).highlight(me.repairHighlightColor);
91975         }
91976         me.dragging = false;
91977     },
91978
91979     /**
91980      * Called before a repair of an invalid drop to get the XY to animate to. By default returns
91981      * the XY of this.dragData.ddel
91982      * @param {Event} e The mouse up event
91983      * @return {Number[]} The xy location (e.g. [100, 200])
91984      */
91985     getRepairXY : function(e){
91986         return Ext.Element.fly(this.dragData.ddel).getXY();
91987     },
91988
91989     destroy : function(){
91990         this.callParent();
91991         if (this.containerScroll) {
91992             Ext.dd.ScrollManager.unregister(this.el);
91993         }
91994     }
91995 });
91996
91997 /**
91998  * @class Ext.dd.ScrollManager
91999  * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
92000  * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
92001  * but you can also override most of the configs per scroll container by adding a
92002  * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
92003  * {@link #vthresh}, {@link #increment} and {@link #frequency}.  Example usage:
92004  * <pre><code>
92005 var el = Ext.get('scroll-ct');
92006 el.ddScrollConfig = {
92007     vthresh: 50,
92008     hthresh: -1,
92009     frequency: 100,
92010     increment: 200
92011 };
92012 Ext.dd.ScrollManager.register(el);
92013 </code></pre>
92014  * Note: This class is designed to be used in "Point Mode
92015  * @singleton
92016  */
92017 Ext.define('Ext.dd.ScrollManager', {
92018     singleton: true,
92019     requires: [
92020         'Ext.dd.DragDropManager'
92021     ],
92022
92023     constructor: function() {
92024         var ddm = Ext.dd.DragDropManager;
92025         ddm.fireEvents = Ext.Function.createSequence(ddm.fireEvents, this.onFire, this);
92026         ddm.stopDrag = Ext.Function.createSequence(ddm.stopDrag, this.onStop, this);
92027         this.doScroll = Ext.Function.bind(this.doScroll, this);
92028         this.ddmInstance = ddm;
92029         this.els = {};
92030         this.dragEl = null;
92031         this.proc = {};
92032     },
92033
92034     onStop: function(e){
92035         var sm = Ext.dd.ScrollManager;
92036         sm.dragEl = null;
92037         sm.clearProc();
92038     },
92039
92040     triggerRefresh: function() {
92041         if (this.ddmInstance.dragCurrent) {
92042             this.ddmInstance.refreshCache(this.ddmInstance.dragCurrent.groups);
92043         }
92044     },
92045
92046     doScroll: function() {
92047         if (this.ddmInstance.dragCurrent) {
92048             var proc   = this.proc,
92049                 procEl = proc.el,
92050                 ddScrollConfig = proc.el.ddScrollConfig,
92051                 inc = ddScrollConfig ? ddScrollConfig.increment : this.increment;
92052
92053             if (!this.animate) {
92054                 if (procEl.scroll(proc.dir, inc)) {
92055                     this.triggerRefresh();
92056                 }
92057             } else {
92058                 procEl.scroll(proc.dir, inc, true, this.animDuration, this.triggerRefresh);
92059             }
92060         }
92061     },
92062
92063     clearProc: function() {
92064         var proc = this.proc;
92065         if (proc.id) {
92066             clearInterval(proc.id);
92067         }
92068         proc.id = 0;
92069         proc.el = null;
92070         proc.dir = "";
92071     },
92072
92073     startProc: function(el, dir) {
92074         this.clearProc();
92075         this.proc.el = el;
92076         this.proc.dir = dir;
92077         var group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup : undefined,
92078             freq  = (el.ddScrollConfig && el.ddScrollConfig.frequency)
92079                   ? el.ddScrollConfig.frequency
92080                   : this.frequency;
92081
92082         if (group === undefined || this.ddmInstance.dragCurrent.ddGroup == group) {
92083             this.proc.id = setInterval(this.doScroll, freq);
92084         }
92085     },
92086
92087     onFire: function(e, isDrop) {
92088         if (isDrop || !this.ddmInstance.dragCurrent) {
92089             return;
92090         }
92091         if (!this.dragEl || this.dragEl != this.ddmInstance.dragCurrent) {
92092             this.dragEl = this.ddmInstance.dragCurrent;
92093             // refresh regions on drag start
92094             this.refreshCache();
92095         }
92096
92097         var xy = e.getXY(),
92098             pt = e.getPoint(),
92099             proc = this.proc,
92100             els = this.els;
92101
92102         for (var id in els) {
92103             var el = els[id], r = el._region;
92104             var c = el.ddScrollConfig ? el.ddScrollConfig : this;
92105             if (r && r.contains(pt) && el.isScrollable()) {
92106                 if (r.bottom - pt.y <= c.vthresh) {
92107                     if(proc.el != el){
92108                         this.startProc(el, "down");
92109                     }
92110                     return;
92111                 }else if (r.right - pt.x <= c.hthresh) {
92112                     if (proc.el != el) {
92113                         this.startProc(el, "left");
92114                     }
92115                     return;
92116                 } else if(pt.y - r.top <= c.vthresh) {
92117                     if (proc.el != el) {
92118                         this.startProc(el, "up");
92119                     }
92120                     return;
92121                 } else if(pt.x - r.left <= c.hthresh) {
92122                     if (proc.el != el) {
92123                         this.startProc(el, "right");
92124                     }
92125                     return;
92126                 }
92127             }
92128         }
92129         this.clearProc();
92130     },
92131
92132     /**
92133      * Registers new overflow element(s) to auto scroll
92134      * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
92135      * The id of or the element to be scrolled or an array of either
92136      */
92137     register : function(el){
92138         if (Ext.isArray(el)) {
92139             for(var i = 0, len = el.length; i < len; i++) {
92140                     this.register(el[i]);
92141             }
92142         } else {
92143             el = Ext.get(el);
92144             this.els[el.id] = el;
92145         }
92146     },
92147
92148     /**
92149      * Unregisters overflow element(s) so they are no longer scrolled
92150      * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
92151      * The id of or the element to be removed or an array of either
92152      */
92153     unregister : function(el){
92154         if(Ext.isArray(el)){
92155             for (var i = 0, len = el.length; i < len; i++) {
92156                 this.unregister(el[i]);
92157             }
92158         }else{
92159             el = Ext.get(el);
92160             delete this.els[el.id];
92161         }
92162     },
92163
92164     /**
92165      * The number of pixels from the top or bottom edge of a container the pointer needs to be to
92166      * trigger scrolling
92167      * @type Number
92168      */
92169     vthresh : 25,
92170     /**
92171      * The number of pixels from the right or left edge of a container the pointer needs to be to
92172      * trigger scrolling
92173      * @type Number
92174      */
92175     hthresh : 25,
92176
92177     /**
92178      * The number of pixels to scroll in each scroll increment
92179      * @type Number
92180      */
92181     increment : 100,
92182
92183     /**
92184      * The frequency of scrolls in milliseconds
92185      * @type Number
92186      */
92187     frequency : 500,
92188
92189     /**
92190      * True to animate the scroll
92191      * @type Boolean
92192      */
92193     animate: true,
92194
92195     /**
92196      * The animation duration in seconds - MUST BE less than Ext.dd.ScrollManager.frequency!
92197      * @type Number
92198      */
92199     animDuration: 0.4,
92200
92201     /**
92202      * The named drag drop {@link Ext.dd.DragSource#ddGroup group} to which this container belongs.
92203      * If a ddGroup is specified, then container scrolling will only occur when a dragged object is in the same ddGroup.
92204      * @type String
92205      */
92206     ddGroup: undefined,
92207
92208     /**
92209      * Manually trigger a cache refresh.
92210      */
92211     refreshCache : function(){
92212         var els = this.els,
92213             id;
92214         for (id in els) {
92215             if(typeof els[id] == 'object'){ // for people extending the object prototype
92216                 els[id]._region = els[id].getRegion();
92217             }
92218         }
92219     }
92220 });
92221
92222 /**
92223  * @class Ext.dd.DropTarget
92224  * @extends Ext.dd.DDTarget
92225  * A simple class that provides the basic implementation needed to make any element a drop target that can have
92226  * draggable items dropped onto it.  The drop has no effect until an implementation of notifyDrop is provided.
92227  */
92228 Ext.define('Ext.dd.DropTarget', {
92229     extend: 'Ext.dd.DDTarget',
92230     requires: ['Ext.dd.ScrollManager'],
92231
92232     /**
92233      * Creates new DropTarget.
92234      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
92235      * @param {Object} config
92236      */
92237     constructor : function(el, config){
92238         this.el = Ext.get(el);
92239
92240         Ext.apply(this, config);
92241
92242         if(this.containerScroll){
92243             Ext.dd.ScrollManager.register(this.el);
92244         }
92245
92246         this.callParent([this.el.dom, this.ddGroup || this.group,
92247               {isTarget: true}]);
92248     },
92249
92250     /**
92251      * @cfg {String} ddGroup
92252      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
92253      * interact with other drag drop objects in the same group.
92254      */
92255     /**
92256      * @cfg {String} [overClass=""]
92257      * The CSS class applied to the drop target element while the drag source is over it.
92258      */
92259     /**
92260      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
92261      * The CSS class returned to the drag source when drop is allowed.
92262      */
92263     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
92264     /**
92265      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
92266      * The CSS class returned to the drag source when drop is not allowed.
92267      */
92268     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
92269
92270     // private
92271     isTarget : true,
92272
92273     // private
92274     isNotifyTarget : true,
92275
92276     /**
92277      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source is now over the
92278      * target.  This default implementation adds the CSS class specified by overClass (if any) to the drop element
92279      * and returns the dropAllowed config value.  This method should be overridden if drop validation is required.
92280      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
92281      * @param {Event} e The event
92282      * @param {Object} data An object containing arbitrary data supplied by the drag source
92283      * @return {String} status The CSS class that communicates the drop status back to the source so that the
92284      * underlying {@link Ext.dd.StatusProxy} can be updated
92285      */
92286     notifyEnter : function(dd, e, data){
92287         if(this.overClass){
92288             this.el.addCls(this.overClass);
92289         }
92290         return this.dropAllowed;
92291     },
92292
92293     /**
92294      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the target.
92295      * This method will be called on every mouse movement while the drag source is over the drop target.
92296      * This default implementation simply returns the dropAllowed config value.
92297      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
92298      * @param {Event} e The event
92299      * @param {Object} data An object containing arbitrary data supplied by the drag source
92300      * @return {String} status The CSS class that communicates the drop status back to the source so that the
92301      * underlying {@link Ext.dd.StatusProxy} can be updated
92302      */
92303     notifyOver : function(dd, e, data){
92304         return this.dropAllowed;
92305     },
92306
92307     /**
92308      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source has been dragged
92309      * out of the target without dropping.  This default implementation simply removes the CSS class specified by
92310      * overClass (if any) from the drop element.
92311      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
92312      * @param {Event} e The event
92313      * @param {Object} data An object containing arbitrary data supplied by the drag source
92314      */
92315     notifyOut : function(dd, e, data){
92316         if(this.overClass){
92317             this.el.removeCls(this.overClass);
92318         }
92319     },
92320
92321     /**
92322      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the dragged item has
92323      * been dropped on it.  This method has no default implementation and returns false, so you must provide an
92324      * implementation that does something to process the drop event and returns true so that the drag source's
92325      * repair action does not run.
92326      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
92327      * @param {Event} e The event
92328      * @param {Object} data An object containing arbitrary data supplied by the drag source
92329      * @return {Boolean} False if the drop was invalid.
92330      */
92331     notifyDrop : function(dd, e, data){
92332         return false;
92333     },
92334
92335     destroy : function(){
92336         this.callParent();
92337         if(this.containerScroll){
92338             Ext.dd.ScrollManager.unregister(this.el);
92339         }
92340     }
92341 });
92342
92343 /**
92344  * @class Ext.dd.Registry
92345  * Provides easy access to all drag drop components that are registered on a page.  Items can be retrieved either
92346  * directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
92347  * @singleton
92348  */
92349 Ext.define('Ext.dd.Registry', {
92350     singleton: true,
92351     constructor: function() {
92352         this.elements = {}; 
92353         this.handles = {}; 
92354         this.autoIdSeed = 0;
92355     },
92356     
92357     getId: function(el, autogen){
92358         if(typeof el == "string"){
92359             return el;
92360         }
92361         var id = el.id;
92362         if(!id && autogen !== false){
92363             id = "extdd-" + (++this.autoIdSeed);
92364             el.id = id;
92365         }
92366         return id;
92367     },
92368     
92369     /**
92370      * Resgister a drag drop element
92371      * @param {String/HTMLElement} element The id or DOM node to register
92372      * @param {Object} data (optional) An custom data object that will be passed between the elements that are involved
92373      * in drag drop operations.  You can populate this object with any arbitrary properties that your own code
92374      * knows how to interpret, plus there are some specific properties known to the Registry that should be
92375      * populated in the data object (if applicable):
92376      * <pre>
92377 Value      Description<br />
92378 ---------  ------------------------------------------<br />
92379 handles    Array of DOM nodes that trigger dragging<br />
92380            for the element being registered<br />
92381 isHandle   True if the element passed in triggers<br />
92382            dragging itself, else false
92383 </pre>
92384      */
92385     register : function(el, data){
92386         data = data || {};
92387         if (typeof el == "string") {
92388             el = document.getElementById(el);
92389         }
92390         data.ddel = el;
92391         this.elements[this.getId(el)] = data;
92392         if (data.isHandle !== false) {
92393             this.handles[data.ddel.id] = data;
92394         }
92395         if (data.handles) {
92396             var hs = data.handles;
92397             for (var i = 0, len = hs.length; i < len; i++) {
92398                 this.handles[this.getId(hs[i])] = data;
92399             }
92400         }
92401     },
92402
92403     /**
92404      * Unregister a drag drop element
92405      * @param {String/HTMLElement} element The id or DOM node to unregister
92406      */
92407     unregister : function(el){
92408         var id = this.getId(el, false);
92409         var data = this.elements[id];
92410         if(data){
92411             delete this.elements[id];
92412             if(data.handles){
92413                 var hs = data.handles;
92414                 for (var i = 0, len = hs.length; i < len; i++) {
92415                     delete this.handles[this.getId(hs[i], false)];
92416                 }
92417             }
92418         }
92419     },
92420
92421     /**
92422      * Returns the handle registered for a DOM Node by id
92423      * @param {String/HTMLElement} id The DOM node or id to look up
92424      * @return {Object} handle The custom handle data
92425      */
92426     getHandle : function(id){
92427         if(typeof id != "string"){ // must be element?
92428             id = id.id;
92429         }
92430         return this.handles[id];
92431     },
92432
92433     /**
92434      * Returns the handle that is registered for the DOM node that is the target of the event
92435      * @param {Event} e The event
92436      * @return {Object} handle The custom handle data
92437      */
92438     getHandleFromEvent : function(e){
92439         var t = e.getTarget();
92440         return t ? this.handles[t.id] : null;
92441     },
92442
92443     /**
92444      * Returns a custom data object that is registered for a DOM node by id
92445      * @param {String/HTMLElement} id The DOM node or id to look up
92446      * @return {Object} data The custom data
92447      */
92448     getTarget : function(id){
92449         if(typeof id != "string"){ // must be element?
92450             id = id.id;
92451         }
92452         return this.elements[id];
92453     },
92454
92455     /**
92456      * Returns a custom data object that is registered for the DOM node that is the target of the event
92457      * @param {Event} e The event
92458      * @return {Object} data The custom data
92459      */
92460     getTargetFromEvent : function(e){
92461         var t = e.getTarget();
92462         return t ? this.elements[t.id] || this.handles[t.id] : null;
92463     }
92464 });
92465 /**
92466  * @class Ext.dd.DropZone
92467  * @extends Ext.dd.DropTarget
92468
92469 This class provides a container DD instance that allows dropping on multiple child target nodes.
92470
92471 By default, this class requires that child nodes accepting drop are registered with {@link Ext.dd.Registry}.
92472 However a simpler way to allow a DropZone to manage any number of target elements is to configure the
92473 DropZone with an implementation of {@link #getTargetFromEvent} which interrogates the passed
92474 mouse event to see if it has taken place within an element, or class of elements. This is easily done
92475 by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
92476 {@link Ext.DomQuery} selector.
92477
92478 Once the DropZone has detected through calling getTargetFromEvent, that the mouse is over
92479 a drop target, that target is passed as the first parameter to {@link #onNodeEnter}, {@link #onNodeOver},
92480 {@link #onNodeOut}, {@link #onNodeDrop}. You may configure the instance of DropZone with implementations
92481 of these methods to provide application-specific behaviour for these events to update both
92482 application state, and UI state.
92483
92484 For example to make a GridPanel a cooperating target with the example illustrated in
92485 {@link Ext.dd.DragZone DragZone}, the following technique might be used:
92486
92487     myGridPanel.on('render', function() {
92488         myGridPanel.dropZone = new Ext.dd.DropZone(myGridPanel.getView().scroller, {
92489
92490             // If the mouse is over a grid row, return that node. This is
92491             // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
92492             getTargetFromEvent: function(e) {
92493                 return e.getTarget(myGridPanel.getView().rowSelector);
92494             },
92495
92496             // On entry into a target node, highlight that node.
92497             onNodeEnter : function(target, dd, e, data){ 
92498                 Ext.fly(target).addCls('my-row-highlight-class');
92499             },
92500
92501             // On exit from a target node, unhighlight that node.
92502             onNodeOut : function(target, dd, e, data){ 
92503                 Ext.fly(target).removeCls('my-row-highlight-class');
92504             },
92505
92506             // While over a target node, return the default drop allowed class which
92507             // places a "tick" icon into the drag proxy.
92508             onNodeOver : function(target, dd, e, data){ 
92509                 return Ext.dd.DropZone.prototype.dropAllowed;
92510             },
92511
92512             // On node drop we can interrogate the target to find the underlying
92513             // application object that is the real target of the dragged data.
92514             // In this case, it is a Record in the GridPanel's Store.
92515             // We can use the data set up by the DragZone's getDragData method to read
92516             // any data we decided to attach in the DragZone's getDragData method.
92517             onNodeDrop : function(target, dd, e, data){
92518                 var rowIndex = myGridPanel.getView().findRowIndex(target);
92519                 var r = myGridPanel.getStore().getAt(rowIndex);
92520                 Ext.Msg.alert('Drop gesture', 'Dropped Record id ' + data.draggedRecord.id +
92521                     ' on Record id ' + r.id);
92522                 return true;
92523             }
92524         });
92525     }
92526
92527 See the {@link Ext.dd.DragZone DragZone} documentation for details about building a DragZone which
92528 cooperates with this DropZone.
92529
92530  * @markdown
92531  */
92532 Ext.define('Ext.dd.DropZone', {
92533     extend: 'Ext.dd.DropTarget',
92534     requires: ['Ext.dd.Registry'],
92535
92536     /**
92537      * Returns a custom data object associated with the DOM node that is the target of the event.  By default
92538      * this looks up the event target in the {@link Ext.dd.Registry}, although you can override this method to
92539      * provide your own custom lookup.
92540      * @param {Event} e The event
92541      * @return {Object} data The custom data
92542      */
92543     getTargetFromEvent : function(e){
92544         return Ext.dd.Registry.getTargetFromEvent(e);
92545     },
92546
92547     /**
92548      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
92549      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
92550      * This method has no default implementation and should be overridden to provide
92551      * node-specific processing if necessary.
92552      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from 
92553      * {@link #getTargetFromEvent} for this node)
92554      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92555      * @param {Event} e The event
92556      * @param {Object} data An object containing arbitrary data supplied by the drag source
92557      */
92558     onNodeEnter : function(n, dd, e, data){
92559         
92560     },
92561
92562     /**
92563      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
92564      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
92565      * The default implementation returns this.dropNotAllowed, so it should be
92566      * overridden to provide the proper feedback.
92567      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
92568      * {@link #getTargetFromEvent} for this node)
92569      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92570      * @param {Event} e The event
92571      * @param {Object} data An object containing arbitrary data supplied by the drag source
92572      * @return {String} status The CSS class that communicates the drop status back to the source so that the
92573      * underlying {@link Ext.dd.StatusProxy} can be updated
92574      */
92575     onNodeOver : function(n, dd, e, data){
92576         return this.dropAllowed;
92577     },
92578
92579     /**
92580      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged out of
92581      * the drop node without dropping.  This method has no default implementation and should be overridden to provide
92582      * node-specific processing if necessary.
92583      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
92584      * {@link #getTargetFromEvent} for this node)
92585      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92586      * @param {Event} e The event
92587      * @param {Object} data An object containing arbitrary data supplied by the drag source
92588      */
92589     onNodeOut : function(n, dd, e, data){
92590         
92591     },
92592
92593     /**
92594      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
92595      * the drop node.  The default implementation returns false, so it should be overridden to provide the
92596      * appropriate processing of the drop event and return true so that the drag source's repair action does not run.
92597      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
92598      * {@link #getTargetFromEvent} for this node)
92599      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92600      * @param {Event} e The event
92601      * @param {Object} data An object containing arbitrary data supplied by the drag source
92602      * @return {Boolean} True if the drop was valid, else false
92603      */
92604     onNodeDrop : function(n, dd, e, data){
92605         return false;
92606     },
92607
92608     /**
92609      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is being dragged over it,
92610      * but not over any of its registered drop nodes.  The default implementation returns this.dropNotAllowed, so
92611      * it should be overridden to provide the proper feedback if necessary.
92612      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92613      * @param {Event} e The event
92614      * @param {Object} data An object containing arbitrary data supplied by the drag source
92615      * @return {String} status The CSS class that communicates the drop status back to the source so that the
92616      * underlying {@link Ext.dd.StatusProxy} can be updated
92617      */
92618     onContainerOver : function(dd, e, data){
92619         return this.dropNotAllowed;
92620     },
92621
92622     /**
92623      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped on it,
92624      * but not on any of its registered drop nodes.  The default implementation returns false, so it should be
92625      * overridden to provide the appropriate processing of the drop event if you need the drop zone itself to
92626      * be able to accept drops.  It should return true when valid so that the drag source's repair action does not run.
92627      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92628      * @param {Event} e The event
92629      * @param {Object} data An object containing arbitrary data supplied by the drag source
92630      * @return {Boolean} True if the drop was valid, else false
92631      */
92632     onContainerDrop : function(dd, e, data){
92633         return false;
92634     },
92635
92636     /**
92637      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source is now over
92638      * the zone.  The default implementation returns this.dropNotAllowed and expects that only registered drop
92639      * nodes can process drag drop operations, so if you need the drop zone itself to be able to process drops
92640      * you should override this method and provide a custom implementation.
92641      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92642      * @param {Event} e The event
92643      * @param {Object} data An object containing arbitrary data supplied by the drag source
92644      * @return {String} status The CSS class that communicates the drop status back to the source so that the
92645      * underlying {@link Ext.dd.StatusProxy} can be updated
92646      */
92647     notifyEnter : function(dd, e, data){
92648         return this.dropNotAllowed;
92649     },
92650
92651     /**
92652      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the drop zone.
92653      * This method will be called on every mouse movement while the drag source is over the drop zone.
92654      * It will call {@link #onNodeOver} while the drag source is over a registered node, and will also automatically
92655      * delegate to the appropriate node-specific methods as necessary when the drag source enters and exits
92656      * registered nodes ({@link #onNodeEnter}, {@link #onNodeOut}). If the drag source is not currently over a
92657      * registered node, it will call {@link #onContainerOver}.
92658      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92659      * @param {Event} e The event
92660      * @param {Object} data An object containing arbitrary data supplied by the drag source
92661      * @return {String} status The CSS class that communicates the drop status back to the source so that the
92662      * underlying {@link Ext.dd.StatusProxy} can be updated
92663      */
92664     notifyOver : function(dd, e, data){
92665         var n = this.getTargetFromEvent(e);
92666         if(!n) { // not over valid drop target
92667             if(this.lastOverNode){
92668                 this.onNodeOut(this.lastOverNode, dd, e, data);
92669                 this.lastOverNode = null;
92670             }
92671             return this.onContainerOver(dd, e, data);
92672         }
92673         if(this.lastOverNode != n){
92674             if(this.lastOverNode){
92675                 this.onNodeOut(this.lastOverNode, dd, e, data);
92676             }
92677             this.onNodeEnter(n, dd, e, data);
92678             this.lastOverNode = n;
92679         }
92680         return this.onNodeOver(n, dd, e, data);
92681     },
92682
92683     /**
92684      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source has been dragged
92685      * out of the zone without dropping.  If the drag source is currently over a registered node, the notification
92686      * will be delegated to {@link #onNodeOut} for node-specific handling, otherwise it will be ignored.
92687      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
92688      * @param {Event} e The event
92689      * @param {Object} data An object containing arbitrary data supplied by the drag zone
92690      */
92691     notifyOut : function(dd, e, data){
92692         if(this.lastOverNode){
92693             this.onNodeOut(this.lastOverNode, dd, e, data);
92694             this.lastOverNode = null;
92695         }
92696     },
92697
92698     /**
92699      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the dragged item has
92700      * been dropped on it.  The drag zone will look up the target node based on the event passed in, and if there
92701      * is a node registered for that event, it will delegate to {@link #onNodeDrop} for node-specific handling,
92702      * otherwise it will call {@link #onContainerDrop}.
92703      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92704      * @param {Event} e The event
92705      * @param {Object} data An object containing arbitrary data supplied by the drag source
92706      * @return {Boolean} False if the drop was invalid.
92707      */
92708     notifyDrop : function(dd, e, data){
92709         if(this.lastOverNode){
92710             this.onNodeOut(this.lastOverNode, dd, e, data);
92711             this.lastOverNode = null;
92712         }
92713         var n = this.getTargetFromEvent(e);
92714         return n ?
92715             this.onNodeDrop(n, dd, e, data) :
92716             this.onContainerDrop(dd, e, data);
92717     },
92718
92719     // private
92720     triggerCacheRefresh : function() {
92721         Ext.dd.DDM.refreshCache(this.groups);
92722     }
92723 });
92724 /**
92725  * @class Ext.flash.Component
92726  * @extends Ext.Component
92727  *
92728  * A simple Component for displaying an Adobe Flash SWF movie. The movie will be sized and can participate
92729  * in layout like any other Component.
92730  *
92731  * This component requires the third-party SWFObject library version 2.2 or above. It is not included within
92732  * the ExtJS distribution, so you will have to include it into your page manually in order to use this component.
92733  * The SWFObject library can be downloaded from the [SWFObject project page](http://code.google.com/p/swfobject)
92734  * and then simply import it into the head of your HTML document:
92735  *
92736  *     <script type="text/javascript" src="path/to/local/swfobject.js"></script>
92737  *
92738  * ## Configuration
92739  *
92740  * This component allows several options for configuring how the target Flash movie is embedded. The most
92741  * important is the required {@link #url} which points to the location of the Flash movie to load. Other
92742  * configurations include:
92743  *
92744  * - {@link #backgroundColor}
92745  * - {@link #wmode}
92746  * - {@link #flashVars}
92747  * - {@link #flashParams}
92748  * - {@link #flashAttributes}
92749  *
92750  * ## Example usage:
92751  *
92752  *     var win = Ext.widget('window', {
92753  *         title: "It's a tiger!",
92754  *         layout: 'fit',
92755  *         width: 300,
92756  *         height: 300,
92757  *         x: 20,
92758  *         y: 20,
92759  *         resizable: true,
92760  *         items: {
92761  *             xtype: 'flash',
92762  *             url: 'tiger.swf'
92763  *         }
92764  *     });
92765  *     win.show();
92766  *
92767  * ## Express Install
92768  *
92769  * Adobe provides a tool called [Express Install](http://www.adobe.com/devnet/flashplayer/articles/express_install.html)
92770  * that offers users an easy way to upgrade their Flash player. If you wish to make use of this, you should set
92771  * the static EXPRESS\_INSTALL\_URL property to the location of your Express Install SWF file:
92772  *
92773  *     Ext.flash.Component.EXPRESS_INSTALL_URL = 'path/to/local/expressInstall.swf';
92774  *
92775  * @docauthor Jason Johnston <jason@sencha.com>
92776  */
92777 Ext.define('Ext.flash.Component', {
92778     extend: 'Ext.Component',
92779     alternateClassName: 'Ext.FlashComponent',
92780     alias: 'widget.flash',
92781
92782     /**
92783      * @cfg {String} flashVersion
92784      * Indicates the version the flash content was published for. Defaults to <tt>'9.0.115'</tt>.
92785      */
92786     flashVersion : '9.0.115',
92787
92788     /**
92789      * @cfg {String} backgroundColor
92790      * The background color of the SWF movie. Defaults to <tt>'#ffffff'</tt>.
92791      */
92792     backgroundColor: '#ffffff',
92793
92794     /**
92795      * @cfg {String} wmode
92796      * The wmode of the flash object. This can be used to control layering. Defaults to <tt>'opaque'</tt>.
92797      * Set to 'transparent' to ignore the {@link #backgroundColor} and make the background of the Flash
92798      * movie transparent.
92799      */
92800     wmode: 'opaque',
92801
92802     /**
92803      * @cfg {Object} flashVars
92804      * A set of key value pairs to be passed to the flash object as flash variables. Defaults to <tt>undefined</tt>.
92805      */
92806
92807     /**
92808      * @cfg {Object} flashParams
92809      * A set of key value pairs to be passed to the flash object as parameters. Possible parameters can be found here:
92810      * http://kb2.adobe.com/cps/127/tn_12701.html Defaults to <tt>undefined</tt>.
92811      */
92812
92813     /**
92814      * @cfg {Object} flashAttributes
92815      * A set of key value pairs to be passed to the flash object as attributes. Defaults to <tt>undefined</tt>.
92816      */
92817
92818     /**
92819      * @cfg {String} url
92820      * The URL of the SWF file to include. Required.
92821      */
92822
92823     /**
92824      * @cfg {String/Number} swfWidth The width of the embedded SWF movie inside the component. Defaults to "100%"
92825      * so that the movie matches the width of the component.
92826      */
92827     swfWidth: '100%',
92828
92829     /**
92830      * @cfg {String/Number} swfHeight The height of the embedded SWF movie inside the component. Defaults to "100%"
92831      * so that the movie matches the height of the component.
92832      */
92833     swfHeight: '100%',
92834
92835     /**
92836      * @cfg {Boolean} expressInstall
92837      * True to prompt the user to install flash if not installed. Note that this uses
92838      * Ext.FlashComponent.EXPRESS_INSTALL_URL, which should be set to the local resource. Defaults to <tt>false</tt>.
92839      */
92840     expressInstall: false,
92841
92842     /**
92843      * @property swf
92844      * @type {Ext.Element}
92845      * A reference to the object or embed element into which the SWF file is loaded. Only
92846      * populated after the component is rendered and the SWF has been successfully embedded.
92847      */
92848
92849     // Have to create a placeholder div with the swfId, which SWFObject will replace with the object/embed element.
92850     renderTpl: ['<div id="{swfId}"></div>'],
92851
92852     initComponent: function() {
92853         if (!('swfobject' in window)) {
92854             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/');
92855         }
92856         if (!this.url) {
92857             Ext.Error.raise('The "url" config is required for Ext.flash.Component');
92858         }
92859
92860         this.callParent();
92861         this.addEvents(
92862             /**
92863              * @event success
92864              * Fired when the Flash movie has been successfully embedded
92865              * @param {Ext.flash.Component} this
92866              */
92867             'success',
92868
92869             /**
92870              * @event failure
92871              * Fired when the Flash movie embedding fails
92872              * @param {Ext.flash.Component} this
92873              */
92874             'failure'
92875         );
92876     },
92877
92878     onRender: function() {
92879         var me = this,
92880             params, vars, undef,
92881             swfId = me.getSwfId();
92882
92883         me.renderData.swfId = swfId;
92884
92885         me.callParent(arguments);
92886
92887         params = Ext.apply({
92888             allowScriptAccess: 'always',
92889             bgcolor: me.backgroundColor,
92890             wmode: me.wmode
92891         }, me.flashParams);
92892
92893         vars = Ext.apply({
92894             allowedDomain: document.location.hostname
92895         }, me.flashVars);
92896
92897         new swfobject.embedSWF(
92898             me.url,
92899             swfId,
92900             me.swfWidth,
92901             me.swfHeight,
92902             me.flashVersion,
92903             me.expressInstall ? me.statics.EXPRESS_INSTALL_URL : undef,
92904             vars,
92905             params,
92906             me.flashAttributes,
92907             Ext.bind(me.swfCallback, me)
92908         );
92909     },
92910
92911     /**
92912      * @private
92913      * The callback method for handling an embedding success or failure by SWFObject
92914      * @param {Object} e The event object passed by SWFObject - see http://code.google.com/p/swfobject/wiki/api
92915      */
92916     swfCallback: function(e) {
92917         var me = this;
92918         if (e.success) {
92919             me.swf = Ext.get(e.ref);
92920             me.onSuccess();
92921             me.fireEvent('success', me);
92922         } else {
92923             me.onFailure();
92924             me.fireEvent('failure', me);
92925         }
92926     },
92927
92928     /**
92929      * Retrieve the id of the SWF object/embed element
92930      */
92931     getSwfId: function() {
92932         return this.swfId || (this.swfId = "extswf" + this.getAutoId());
92933     },
92934
92935     onSuccess: function() {
92936         // swfobject forces visiblity:visible on the swf element, which prevents it 
92937         // from getting hidden when an ancestor is given visibility:hidden.
92938         this.swf.setStyle('visibility', 'inherit');
92939     },
92940
92941     onFailure: Ext.emptyFn,
92942
92943     beforeDestroy: function() {
92944         var me = this,
92945             swf = me.swf;
92946         if (swf) {
92947             swfobject.removeSWF(me.getSwfId());
92948             Ext.destroy(swf);
92949             delete me.swf;
92950         }
92951         me.callParent();
92952     },
92953
92954     statics: {
92955         /**
92956          * Sets the url for installing flash if it doesn't exist. This should be set to a local resource.
92957          * See http://www.adobe.com/devnet/flashplayer/articles/express_install.html for details.
92958          * @static
92959          * @type String
92960          */
92961         EXPRESS_INSTALL_URL: 'http:/' + '/swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf'
92962     }
92963 });
92964
92965 /**
92966  * @class Ext.form.action.Action
92967  * @extends Ext.Base
92968  * <p>The subclasses of this class provide actions to perform upon {@link Ext.form.Basic Form}s.</p>
92969  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
92970  * the Form needs to perform an action such as submit or load. The Configuration options
92971  * listed for this class are set through the Form's action methods: {@link Ext.form.Basic#submit submit},
92972  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}</p>
92973  * <p>The instance of Action which performed the action is passed to the success
92974  * and failure callbacks of the Form's action methods ({@link Ext.form.Basic#submit submit},
92975  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}),
92976  * and to the {@link Ext.form.Basic#actioncomplete actioncomplete} and
92977  * {@link Ext.form.Basic#actionfailed actionfailed} event handlers.</p>
92978  */
92979 Ext.define('Ext.form.action.Action', {
92980     alternateClassName: 'Ext.form.Action',
92981
92982     /**
92983      * @cfg {Ext.form.Basic} form The {@link Ext.form.Basic BasicForm} instance that
92984      * is invoking this Action. Required.
92985      */
92986
92987     /**
92988      * @cfg {String} url The URL that the Action is to invoke. Will default to the {@link Ext.form.Basic#url url}
92989      * configured on the {@link #form}.
92990      */
92991
92992     /**
92993      * @cfg {Boolean} reset When set to <tt><b>true</b></tt>, causes the Form to be
92994      * {@link Ext.form.Basic#reset reset} on Action success. If specified, this happens
92995      * before the {@link #success} callback is called and before the Form's
92996      * {@link Ext.form.Basic#actioncomplete actioncomplete} event fires.
92997      */
92998
92999     /**
93000      * @cfg {String} method The HTTP method to use to access the requested URL. Defaults to the
93001      * {@link Ext.form.Basic#method BasicForm's method}, or 'POST' if not specified.
93002      */
93003
93004     /**
93005      * @cfg {Object/String} params <p>Extra parameter values to pass. These are added to the Form's
93006      * {@link Ext.form.Basic#baseParams} and passed to the specified URL along with the Form's
93007      * input fields.</p>
93008      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p>
93009      */
93010
93011     /**
93012      * @cfg {Object} headers <p>Extra headers to be sent in the AJAX request for submit and load actions. See
93013      * {@link Ext.data.proxy.Ajax#headers}.</p>
93014      */
93015
93016     /**
93017      * @cfg {Number} timeout The number of seconds to wait for a server response before
93018      * failing with the {@link #failureType} as {@link Ext.form.action.Action#CONNECT_FAILURE}. If not specified,
93019      * defaults to the configured <tt>{@link Ext.form.Basic#timeout timeout}</tt> of the
93020      * {@link #form}.
93021      */
93022
93023     /**
93024      * @cfg {Function} success The function to call when a valid success return packet is received.
93025      * The function is passed the following parameters:<ul class="mdetail-params">
93026      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
93027      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. The {@link #result}
93028      * property of this object may be examined to perform custom postprocessing.</div></li>
93029      * </ul>
93030      */
93031
93032     /**
93033      * @cfg {Function} failure The function to call when a failure packet was received, or when an
93034      * error ocurred in the Ajax communication.
93035      * The function is passed the following parameters:<ul class="mdetail-params">
93036      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
93037      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. If an Ajax
93038      * error ocurred, the failure type will be in {@link #failureType}. The {@link #result}
93039      * property of this object may be examined to perform custom postprocessing.</div></li>
93040      * </ul>
93041      */
93042
93043     /**
93044      * @cfg {Object} scope The scope in which to call the configured <tt>success</tt> and <tt>failure</tt>
93045      * callback functions (the <tt>this</tt> reference for the callback functions).
93046      */
93047
93048     /**
93049      * @cfg {String} waitMsg The message to be displayed by a call to {@link Ext.window.MessageBox#wait}
93050      * during the time the action is being processed.
93051      */
93052
93053     /**
93054      * @cfg {String} waitTitle The title to be displayed by a call to {@link Ext.window.MessageBox#wait}
93055      * during the time the action is being processed.
93056      */
93057
93058     /**
93059      * @cfg {Boolean} submitEmptyText If set to <tt>true</tt>, the emptyText value will be sent with the form
93060      * when it is submitted. Defaults to <tt>true</tt>.
93061      */
93062     submitEmptyText : true,
93063     /**
93064      * @property type
93065      * The type of action this Action instance performs.
93066      * Currently only "submit" and "load" are supported.
93067      * @type {String}
93068      */
93069
93070     /**
93071      * The type of failure detected will be one of these: {@link Ext.form.action.Action#CLIENT_INVALID},
93072      * {@link Ext.form.action.Action#SERVER_INVALID}, {@link Ext.form.action.Action#CONNECT_FAILURE}, or
93073      * {@link Ext.form.action.Action#LOAD_FAILURE}.  Usage:
93074      * <pre><code>
93075 var fp = new Ext.form.Panel({
93076 ...
93077 buttons: [{
93078     text: 'Save',
93079     formBind: true,
93080     handler: function(){
93081         if(fp.getForm().isValid()){
93082             fp.getForm().submit({
93083                 url: 'form-submit.php',
93084                 waitMsg: 'Submitting your data...',
93085                 success: function(form, action){
93086                     // server responded with success = true
93087                     var result = action.{@link #result};
93088                 },
93089                 failure: function(form, action){
93090                     if (action.{@link #failureType} === {@link Ext.form.action.Action#CONNECT_FAILURE}) {
93091                         Ext.Msg.alert('Error',
93092                             'Status:'+action.{@link #response}.status+': '+
93093                             action.{@link #response}.statusText);
93094                     }
93095                     if (action.failureType === {@link Ext.form.action.Action#SERVER_INVALID}){
93096                         // server responded with success = false
93097                         Ext.Msg.alert('Invalid', action.{@link #result}.errormsg);
93098                     }
93099                 }
93100             });
93101         }
93102     }
93103 },{
93104     text: 'Reset',
93105     handler: function(){
93106         fp.getForm().reset();
93107     }
93108 }]
93109      * </code></pre>
93110      * @property failureType
93111      * @type {String}
93112      */
93113
93114     /**
93115      * The raw XMLHttpRequest object used to perform the action.
93116      * @property response
93117      * @type {Object}
93118      */
93119
93120     /**
93121      * The decoded response object containing a boolean <tt>success</tt> property and
93122      * other, action-specific properties.
93123      * @property result
93124      * @type {Object}
93125      */
93126
93127     /**
93128      * Creates new Action.
93129      * @param {Object} config (optional) Config object.
93130      */
93131     constructor: function(config) {
93132         if (config) {
93133             Ext.apply(this, config);
93134         }
93135
93136         // Normalize the params option to an Object
93137         var params = config.params;
93138         if (Ext.isString(params)) {
93139             this.params = Ext.Object.fromQueryString(params);
93140         }
93141     },
93142
93143     /**
93144      * Invokes this action using the current configuration.
93145      */
93146     run: Ext.emptyFn,
93147
93148     /**
93149      * @private
93150      * @method onSuccess
93151      * Callback method that gets invoked when the action completes successfully. Must be implemented by subclasses.
93152      * @param {Object} response
93153      */
93154
93155     /**
93156      * @private
93157      * @method handleResponse
93158      * Handles the raw response and builds a result object from it. Must be implemented by subclasses.
93159      * @param {Object} response
93160      */
93161
93162     /**
93163      * @private
93164      * Handles a failure response.
93165      * @param {Object} response
93166      */
93167     onFailure : function(response){
93168         this.response = response;
93169         this.failureType = Ext.form.action.Action.CONNECT_FAILURE;
93170         this.form.afterAction(this, false);
93171     },
93172
93173     /**
93174      * @private
93175      * Validates that a response contains either responseText or responseXML and invokes
93176      * {@link #handleResponse} to build the result object.
93177      * @param {Object} response The raw response object.
93178      * @return {Object/Boolean} result The result object as built by handleResponse, or <tt>true</tt> if
93179      *                         the response had empty responseText and responseXML.
93180      */
93181     processResponse : function(response){
93182         this.response = response;
93183         if (!response.responseText && !response.responseXML) {
93184             return true;
93185         }
93186         return (this.result = this.handleResponse(response));
93187     },
93188
93189     /**
93190      * @private
93191      * Build the URL for the AJAX request. Used by the standard AJAX submit and load actions.
93192      * @return {String} The URL.
93193      */
93194     getUrl: function() {
93195         return this.url || this.form.url;
93196     },
93197
93198     /**
93199      * @private
93200      * Determine the HTTP method to be used for the request.
93201      * @return {String} The HTTP method
93202      */
93203     getMethod: function() {
93204         return (this.method || this.form.method || 'POST').toUpperCase();
93205     },
93206
93207     /**
93208      * @private
93209      * Get the set of parameters specified in the BasicForm's baseParams and/or the params option.
93210      * Items in params override items of the same name in baseParams.
93211      * @return {Object} the full set of parameters
93212      */
93213     getParams: function() {
93214         return Ext.apply({}, this.params, this.form.baseParams);
93215     },
93216
93217     /**
93218      * @private
93219      * Creates a callback object.
93220      */
93221     createCallback: function() {
93222         var me = this,
93223             undef,
93224             form = me.form;
93225         return {
93226             success: me.onSuccess,
93227             failure: me.onFailure,
93228             scope: me,
93229             timeout: (this.timeout * 1000) || (form.timeout * 1000),
93230             upload: form.fileUpload ? me.onSuccess : undef
93231         };
93232     },
93233
93234     statics: {
93235         /**
93236          * @property CLIENT_INVALID
93237          * Failure type returned when client side validation of the Form fails
93238          * thus aborting a submit action. Client side validation is performed unless
93239          * {@link Ext.form.action.Submit#clientValidation} is explicitly set to <tt>false</tt>.
93240          * @type {String}
93241          * @static
93242          */
93243         CLIENT_INVALID: 'client',
93244
93245         /**
93246          * @property SERVER_INVALID
93247          * <p>Failure type returned when server side processing fails and the {@link #result}'s
93248          * <tt>success</tt> property is set to <tt>false</tt>.</p>
93249          * <p>In the case of a form submission, field-specific error messages may be returned in the
93250          * {@link #result}'s <tt>errors</tt> property.</p>
93251          * @type {String}
93252          * @static
93253          */
93254         SERVER_INVALID: 'server',
93255
93256         /**
93257          * @property CONNECT_FAILURE
93258          * Failure type returned when a communication error happens when attempting
93259          * to send a request to the remote server. The {@link #response} may be examined to
93260          * provide further information.
93261          * @type {String}
93262          * @static
93263          */
93264         CONNECT_FAILURE: 'connect',
93265
93266         /**
93267          * @property LOAD_FAILURE
93268          * Failure type returned when the response's <tt>success</tt>
93269          * property is set to <tt>false</tt>, or no field values are returned in the response's
93270          * <tt>data</tt> property.
93271          * @type {String}
93272          * @static
93273          */
93274         LOAD_FAILURE: 'load'
93275
93276
93277     }
93278 });
93279
93280 /**
93281  * @class Ext.form.action.Submit
93282  * @extends Ext.form.action.Action
93283  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s
93284  * and processes the returned response.</p>
93285  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
93286  * {@link Ext.form.Basic#submit submit}ting.</p>
93287  * <p><u><b>Response Packet Criteria</b></u></p>
93288  * <p>A response packet may contain:
93289  * <div class="mdetail-params"><ul>
93290  * <li><b><code>success</code></b> property : Boolean
93291  * <div class="sub-desc">The <code>success</code> property is required.</div></li>
93292  * <li><b><code>errors</code></b> property : Object
93293  * <div class="sub-desc"><div class="sub-desc">The <code>errors</code> property,
93294  * which is optional, contains error messages for invalid fields.</div></li>
93295  * </ul></div>
93296  * <p><u><b>JSON Packets</b></u></p>
93297  * <p>By default, response packets are assumed to be JSON, so a typical response
93298  * packet may look like this:</p><pre><code>
93299 {
93300     success: false,
93301     errors: {
93302         clientCode: "Client not found",
93303         portOfLoading: "This field must not be null"
93304     }
93305 }</code></pre>
93306  * <p>Other data may be placed into the response for processing by the {@link Ext.form.Basic}'s callback
93307  * or event handler methods. The object decoded from this JSON is available in the
93308  * {@link Ext.form.action.Action#result result} property.</p>
93309  * <p>Alternatively, if an {@link Ext.form.Basic#errorReader errorReader} is specified as an {@link Ext.data.reader.Xml XmlReader}:</p><pre><code>
93310     errorReader: new Ext.data.reader.Xml({
93311             record : 'field',
93312             success: '@success'
93313         }, [
93314             'id', 'msg'
93315         ]
93316     )
93317 </code></pre>
93318  * <p>then the results may be sent back in XML format:</p><pre><code>
93319 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
93320 &lt;message success="false"&gt;
93321 &lt;errors&gt;
93322     &lt;field&gt;
93323         &lt;id&gt;clientCode&lt;/id&gt;
93324         &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;
93325     &lt;/field&gt;
93326     &lt;field&gt;
93327         &lt;id&gt;portOfLoading&lt;/id&gt;
93328         &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;
93329     &lt;/field&gt;
93330 &lt;/errors&gt;
93331 &lt;/message&gt;
93332 </code></pre>
93333  * <p>Other elements may be placed into the response XML for processing by the {@link Ext.form.Basic}'s callback
93334  * or event handler methods. The XML document is available in the {@link Ext.form.Basic#errorReader errorReader}'s
93335  * {@link Ext.data.reader.Xml#xmlData xmlData} property.</p>
93336  */
93337 Ext.define('Ext.form.action.Submit', {
93338     extend:'Ext.form.action.Action',
93339     alternateClassName: 'Ext.form.Action.Submit',
93340     alias: 'formaction.submit',
93341
93342     type: 'submit',
93343
93344     /**
93345      * @cfg {Boolean} clientValidation Determines whether a Form's fields are validated
93346      * in a final call to {@link Ext.form.Basic#isValid isValid} prior to submission.
93347      * Pass <tt>false</tt> in the Form's submit options to prevent this. Defaults to true.
93348      */
93349
93350     // inherit docs
93351     run : function(){
93352         var form = this.form;
93353         if (this.clientValidation === false || form.isValid()) {
93354             this.doSubmit();
93355         } else {
93356             // client validation failed
93357             this.failureType = Ext.form.action.Action.CLIENT_INVALID;
93358             form.afterAction(this, false);
93359         }
93360     },
93361
93362     /**
93363      * @private
93364      * Perform the submit of the form data.
93365      */
93366     doSubmit: function() {
93367         var formEl,
93368             ajaxOptions = Ext.apply(this.createCallback(), {
93369                 url: this.getUrl(),
93370                 method: this.getMethod(),
93371                 headers: this.headers
93372             });
93373
93374         // For uploads we need to create an actual form that contains the file upload fields,
93375         // and pass that to the ajax call so it can do its iframe-based submit method.
93376         if (this.form.hasUpload()) {
93377             formEl = ajaxOptions.form = this.buildForm();
93378             ajaxOptions.isUpload = true;
93379         } else {
93380             ajaxOptions.params = this.getParams();
93381         }
93382
93383         Ext.Ajax.request(ajaxOptions);
93384
93385         if (formEl) {
93386             Ext.removeNode(formEl);
93387         }
93388     },
93389
93390     /**
93391      * @private
93392      * Build the full set of parameters from the field values plus any additional configured params.
93393      */
93394     getParams: function() {
93395         var nope = false,
93396             configParams = this.callParent(),
93397             fieldParams = this.form.getValues(nope, nope, this.submitEmptyText !== nope);
93398         return Ext.apply({}, fieldParams, configParams);
93399     },
93400
93401     /**
93402      * @private
93403      * Build a form element containing fields corresponding to all the parameters to be
93404      * submitted (everything returned by {@link #getParams}.
93405      * NOTE: the form element is automatically added to the DOM, so any code that uses
93406      * it must remove it from the DOM after finishing with it.
93407      * @return HTMLFormElement
93408      */
93409     buildForm: function() {
93410         var fieldsSpec = [],
93411             formSpec,
93412             formEl,
93413             basicForm = this.form,
93414             params = this.getParams(),
93415             uploadFields = [];
93416
93417         basicForm.getFields().each(function(field) {
93418             if (field.isFileUpload()) {
93419                 uploadFields.push(field);
93420             }
93421         });
93422
93423         function addField(name, val) {
93424             fieldsSpec.push({
93425                 tag: 'input',
93426                 type: 'hidden',
93427                 name: name,
93428                 value: Ext.String.htmlEncode(val)
93429             });
93430         }
93431
93432         // Add the form field values
93433         Ext.iterate(params, function(key, val) {
93434             if (Ext.isArray(val)) {
93435                 Ext.each(val, function(v) {
93436                     addField(key, v);
93437                 });
93438             } else {
93439                 addField(key, val);
93440             }
93441         });
93442
93443         formSpec = {
93444             tag: 'form',
93445             action: this.getUrl(),
93446             method: this.getMethod(),
93447             target: this.target || '_self',
93448             style: 'display:none',
93449             cn: fieldsSpec
93450         };
93451
93452         // Set the proper encoding for file uploads
93453         if (uploadFields.length) {
93454             formSpec.encoding = formSpec.enctype = 'multipart/form-data';
93455         }
93456
93457         // Create the form
93458         formEl = Ext.DomHelper.append(Ext.getBody(), formSpec);
93459
93460         // Special handling for file upload fields: since browser security measures prevent setting
93461         // their values programatically, and prevent carrying their selected values over when cloning,
93462         // we have to move the actual field instances out of their components and into the form.
93463         Ext.Array.each(uploadFields, function(field) {
93464             if (field.rendered) { // can only have a selected file value after being rendered
93465                 formEl.appendChild(field.extractFileInput());
93466             }
93467         });
93468
93469         return formEl;
93470     },
93471
93472
93473
93474     /**
93475      * @private
93476      */
93477     onSuccess: function(response) {
93478         var form = this.form,
93479             success = true,
93480             result = this.processResponse(response);
93481         if (result !== true && !result.success) {
93482             if (result.errors) {
93483                 form.markInvalid(result.errors);
93484             }
93485             this.failureType = Ext.form.action.Action.SERVER_INVALID;
93486             success = false;
93487         }
93488         form.afterAction(this, success);
93489     },
93490
93491     /**
93492      * @private
93493      */
93494     handleResponse: function(response) {
93495         var form = this.form,
93496             errorReader = form.errorReader,
93497             rs, errors, i, len, records;
93498         if (errorReader) {
93499             rs = errorReader.read(response);
93500             records = rs.records;
93501             errors = [];
93502             if (records) {
93503                 for(i = 0, len = records.length; i < len; i++) {
93504                     errors[i] = records[i].data;
93505                 }
93506             }
93507             if (errors.length < 1) {
93508                 errors = null;
93509             }
93510             return {
93511                 success : rs.success,
93512                 errors : errors
93513             };
93514         }
93515         return Ext.decode(response.responseText);
93516     }
93517 });
93518
93519 /**
93520  * @class Ext.util.ComponentDragger
93521  * @extends Ext.dd.DragTracker
93522  * <p>A subclass of Ext.dd.DragTracker which handles dragging any Component.</p>
93523  * <p>This is configured with a Component to be made draggable, and a config object for the
93524  * {@link Ext.dd.DragTracker} class.</p>
93525  * <p>A {@link #delegate} may be provided which may be either the element to use as the mousedown target
93526  * or a {@link Ext.DomQuery} selector to activate multiple mousedown targets.</p>
93527  */
93528 Ext.define('Ext.util.ComponentDragger', {
93529
93530     /**
93531      * @cfg {Boolean} constrain
93532      * Specify as <code>true</code> to constrain the Component to within the bounds of the {@link #constrainTo} region.
93533      */
93534
93535     /**
93536      * @cfg {String/Ext.Element} delegate
93537      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the Component's encapsulating
93538      * Element which are the drag handles. This limits dragging to only begin when the matching elements are mousedowned.</p>
93539      * <p>This may also be a specific child element within the Component's encapsulating element to use as the drag handle.</p>
93540      */
93541
93542     /**
93543      * @cfg {Boolean} constrainDelegate
93544      * Specify as <code>true</code> to constrain the drag handles within the {@link #constrainTo} region.
93545      */
93546
93547     extend: 'Ext.dd.DragTracker',
93548
93549     autoStart: 500,
93550
93551     /**
93552      * Creates new ComponentDragger.
93553      * @param {Object} comp The Component to provide dragging for.
93554      * @param {Object} config (optional) Config object
93555      */
93556     constructor: function(comp, config) {
93557         this.comp = comp;
93558         this.initialConstrainTo = config.constrainTo;
93559         this.callParent([ config ]);
93560     },
93561
93562     onStart: function(e) {
93563         var me = this,
93564             comp = me.comp;
93565
93566         // Cache the start [X, Y] array
93567         this.startPosition = comp.getPosition();
93568
93569         // If client Component has a ghost method to show a lightweight version of itself
93570         // then use that as a drag proxy unless configured to liveDrag.
93571         if (comp.ghost && !comp.liveDrag) {
93572              me.proxy = comp.ghost();
93573              me.dragTarget = me.proxy.header.el;
93574         }
93575
93576         // Set the constrainTo Region before we start dragging.
93577         if (me.constrain || me.constrainDelegate) {
93578             me.constrainTo = me.calculateConstrainRegion();
93579         }
93580     },
93581
93582     calculateConstrainRegion: function() {
93583         var me = this,
93584             comp = me.comp,
93585             c = me.initialConstrainTo,
93586             delegateRegion,
93587             elRegion,
93588             shadowSize = comp.el.shadow ? comp.el.shadow.offset : 0;
93589
93590         // The configured constrainTo might be a Region or an element
93591         if (!(c instanceof Ext.util.Region)) {
93592             c =  Ext.fly(c).getViewRegion();
93593         }
93594
93595         // Reduce the constrain region to allow for shadow
93596         if (shadowSize) {
93597             c.adjust(0, -shadowSize, -shadowSize, shadowSize);
93598         }
93599
93600         // If they only want to constrain the *delegate* to within the constrain region,
93601         // adjust the region to be larger based on the insets of the delegate from the outer
93602         // edges of the Component.
93603         if (!me.constrainDelegate) {
93604             delegateRegion = Ext.fly(me.dragTarget).getRegion();
93605             elRegion = me.proxy ? me.proxy.el.getRegion() : comp.el.getRegion();
93606
93607             c.adjust(
93608                 delegateRegion.top - elRegion.top,
93609                 delegateRegion.right - elRegion.right,
93610                 delegateRegion.bottom - elRegion.bottom,
93611                 delegateRegion.left - elRegion.left
93612             );
93613         }
93614         return c;
93615     },
93616
93617     // Move either the ghost Component or the target Component to its new position on drag
93618     onDrag: function(e) {
93619         var me = this,
93620             comp = (me.proxy && !me.comp.liveDrag) ? me.proxy : me.comp,
93621             offset = me.getOffset(me.constrain || me.constrainDelegate ? 'dragTarget' : null);
93622
93623         comp.setPosition(me.startPosition[0] + offset[0], me.startPosition[1] + offset[1]);
93624     },
93625
93626     onEnd: function(e) {
93627         if (this.proxy && !this.comp.liveDrag) {
93628             this.comp.unghost();
93629         }
93630     }
93631 });
93632 /**
93633  * A mixin which allows a component to be configured and decorated with a label and/or error message as is
93634  * common for form fields. This is used by e.g. Ext.form.field.Base and Ext.form.FieldContainer
93635  * to let them be managed by the Field layout.
93636  *
93637  * NOTE: This mixin is mainly for internal library use and most users should not need to use it directly. It
93638  * is more likely you will want to use one of the component classes that import this mixin, such as
93639  * Ext.form.field.Base or Ext.form.FieldContainer.
93640  *
93641  * Use of this mixin does not make a component a field in the logical sense, meaning it does not provide any
93642  * logic or state related to values or validation; that is handled by the related Ext.form.field.Field
93643  * mixin. These two mixins may be used separately (for example Ext.form.FieldContainer is Labelable but not a
93644  * Field), or in combination (for example Ext.form.field.Base implements both and has logic for connecting the
93645  * two.)
93646  *
93647  * Component classes which use this mixin should use the Field layout
93648  * or a derivation thereof to properly size and position the label and message according to the component config.
93649  * They must also call the {@link #initLabelable} method during component initialization to ensure the mixin gets
93650  * set up correctly.
93651  *
93652  * @docauthor Jason Johnston <jason@sencha.com>
93653  */
93654 Ext.define("Ext.form.Labelable", {
93655     requires: ['Ext.XTemplate'],
93656
93657     /**
93658      * @cfg {String/String[]/Ext.XTemplate} labelableRenderTpl
93659      * The rendering template for the field decorations. Component classes using this mixin should include
93660      * logic to use this as their {@link Ext.AbstractComponent#renderTpl renderTpl}, and implement the
93661      * {@link #getSubTplMarkup} method to generate the field body content.
93662      */
93663     labelableRenderTpl: [
93664         '<tpl if="!hideLabel && !(!fieldLabel && hideEmptyLabel)">',
93665             '<label id="{id}-labelEl"<tpl if="inputId"> for="{inputId}"</tpl> class="{labelCls}"',
93666                 '<tpl if="labelStyle"> style="{labelStyle}"</tpl>>',
93667                 '<tpl if="fieldLabel">{fieldLabel}{labelSeparator}</tpl>',
93668             '</label>',
93669         '</tpl>',
93670         '<div class="{baseBodyCls} {fieldBodyCls}" id="{id}-bodyEl" role="presentation">{subTplMarkup}</div>',
93671         '<div id="{id}-errorEl" class="{errorMsgCls}" style="display:none"></div>',
93672         '<div class="{clearCls}" role="presentation"><!-- --></div>',
93673         {
93674             compiled: true,
93675             disableFormats: true
93676         }
93677     ],
93678
93679     /**
93680      * @cfg {Ext.XTemplate} activeErrorsTpl
93681      * The template used to format the Array of error messages passed to {@link #setActiveErrors}
93682      * into a single HTML string. By default this renders each message as an item in an unordered list.
93683      */
93684     activeErrorsTpl: [
93685         '<tpl if="errors && errors.length">',
93686             '<ul><tpl for="errors"><li<tpl if="xindex == xcount"> class="last"</tpl>>{.}</li></tpl></ul>',
93687         '</tpl>'
93688     ],
93689
93690     /**
93691      * @property isFieldLabelable
93692      * @type Boolean
93693      * Flag denoting that this object is labelable as a field. Always true.
93694      */
93695     isFieldLabelable: true,
93696
93697     /**
93698      * @cfg {String} [formItemCls='x-form-item']
93699      * A CSS class to be applied to the outermost element to denote that it is participating in the form
93700      * field layout.
93701      */
93702     formItemCls: Ext.baseCSSPrefix + 'form-item',
93703
93704     /**
93705      * @cfg {String} [labelCls='x-form-item-label']
93706      * The CSS class to be applied to the label element.
93707      * This (single) CSS class is used to formulate the renderSelector and drives the field
93708      * layout where it is concatenated with a hyphen ('-') and {@link #labelAlign}. To add
93709      * additional classes, use {@link #labelClsExtra}.
93710      */
93711     labelCls: Ext.baseCSSPrefix + 'form-item-label',
93712
93713     /**
93714      * @cfg {String} labelClsExtra
93715      * An optional string of one or more additional CSS classes to add to the label element.
93716      * Defaults to empty.
93717      */
93718
93719     /**
93720      * @cfg {String} [errorMsgCls='x-form-error-msg']
93721      * The CSS class to be applied to the error message element.
93722      */
93723     errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',
93724
93725     /**
93726      * @cfg {String} [baseBodyCls='x-form-item-body']
93727      * The CSS class to be applied to the body content element.
93728      */
93729     baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',
93730
93731     /**
93732      * @cfg {String} fieldBodyCls
93733      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
93734      */
93735     fieldBodyCls: '',
93736
93737     /**
93738      * @cfg {String} [clearCls='x-clear']
93739      * The CSS class to be applied to the special clearing div rendered directly after the field
93740      * contents wrapper to provide field clearing.
93741      */
93742     clearCls: Ext.baseCSSPrefix + 'clear',
93743
93744     /**
93745      * @cfg {String} [invalidCls='x-form-invalid']
93746      * The CSS class to use when marking the component invalid.
93747      */
93748     invalidCls : Ext.baseCSSPrefix + 'form-invalid',
93749
93750     /**
93751      * @cfg {String} fieldLabel
93752      * The label for the field. It gets appended with the {@link #labelSeparator}, and its position
93753      * and sizing is determined by the {@link #labelAlign}, {@link #labelWidth}, and {@link #labelPad}
93754      * configs.
93755      */
93756     fieldLabel: undefined,
93757
93758     /**
93759      * @cfg {String} labelAlign
93760      * <p>Controls the position and alignment of the {@link #fieldLabel}. Valid values are:</p>
93761      * <ul>
93762      * <li><tt>"left"</tt> (the default) - The label is positioned to the left of the field, with its text
93763      * aligned to the left. Its width is determined by the {@link #labelWidth} config.</li>
93764      * <li><tt>"top"</tt> - The label is positioned above the field.</li>
93765      * <li><tt>"right"</tt> - The label is positioned to the left of the field, with its text aligned
93766      * to the right. Its width is determined by the {@link #labelWidth} config.</li>
93767      * </ul>
93768      */
93769     labelAlign : 'left',
93770
93771     /**
93772      * @cfg {Number} labelWidth
93773      * The width of the {@link #fieldLabel} in pixels. Only applicable if the {@link #labelAlign} is set
93774      * to "left" or "right".
93775      */
93776     labelWidth: 100,
93777
93778     /**
93779      * @cfg {Number} labelPad
93780      * The amount of space in pixels between the {@link #fieldLabel} and the input field.
93781      */
93782     labelPad : 5,
93783
93784     /**
93785      * @cfg {String} labelSeparator
93786      * Character(s) to be inserted at the end of the {@link #fieldLabel label text}.
93787      */
93788     labelSeparator : ':',
93789
93790     /**
93791      * @cfg {String} labelStyle
93792      * A CSS style specification string to apply directly to this field's label.
93793      */
93794
93795     /**
93796      * @cfg {Boolean} hideLabel
93797      * Set to true to completely hide the label element ({@link #fieldLabel} and {@link #labelSeparator}).
93798      * Also see {@link #hideEmptyLabel}, which controls whether space will be reserved for an empty fieldLabel.
93799      */
93800     hideLabel: false,
93801
93802     /**
93803      * @cfg {Boolean} hideEmptyLabel
93804      * <p>When set to <tt>true</tt>, the label element ({@link #fieldLabel} and {@link #labelSeparator}) will be
93805      * automatically hidden if the {@link #fieldLabel} is empty. Setting this to <tt>false</tt> will cause the empty
93806      * label element to be rendered and space to be reserved for it; this is useful if you want a field without a label
93807      * to line up with other labeled fields in the same form.</p>
93808      * <p>If you wish to unconditionall hide the label even if a non-empty fieldLabel is configured, then set
93809      * the {@link #hideLabel} config to <tt>true</tt>.</p>
93810      */
93811     hideEmptyLabel: true,
93812
93813     /**
93814      * @cfg {Boolean} preventMark
93815      * <tt>true</tt> to disable displaying any {@link #setActiveError error message} set on this object.
93816      */
93817     preventMark: false,
93818
93819     /**
93820      * @cfg {Boolean} autoFitErrors
93821      * Whether to adjust the component's body area to make room for 'side' or 'under'
93822      * {@link #msgTarget error messages}.
93823      */
93824     autoFitErrors: true,
93825
93826     /**
93827      * @cfg {String} msgTarget <p>The location where the error message text should display.
93828      * Must be one of the following values:</p>
93829      * <div class="mdetail-params"><ul>
93830      * <li><code>qtip</code> Display a quick tip containing the message when the user hovers over the field. This is the default.
93831      * <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>
93832      * <li><code>title</code> Display the message in a default browser title attribute popup.</li>
93833      * <li><code>under</code> Add a block div beneath the field containing the error message.</li>
93834      * <li><code>side</code> Add an error icon to the right of the field, displaying the message in a popup on hover.</li>
93835      * <li><code>none</code> Don't display any error message. This might be useful if you are implementing custom error display.</li>
93836      * <li><code>[element id]</code> Add the error message directly to the innerHTML of the specified element.</li>
93837      * </ul></div>
93838      */
93839     msgTarget: 'qtip',
93840
93841     /**
93842      * @cfg {String} activeError
93843      * If specified, then the component will be displayed with this value as its active error when
93844      * first rendered. Use {@link #setActiveError} or {@link #unsetActiveError} to
93845      * change it after component creation.
93846      */
93847
93848
93849     /**
93850      * Performs initialization of this mixin. Component classes using this mixin should call this method
93851      * during their own initialization.
93852      */
93853     initLabelable: function() {
93854         this.addCls(this.formItemCls);
93855
93856         this.addEvents(
93857             /**
93858              * @event errorchange
93859              * Fires when the active error message is changed via {@link #setActiveError}.
93860              * @param {Ext.form.Labelable} this
93861              * @param {String} error The active error message
93862              */
93863             'errorchange'
93864         );
93865     },
93866
93867     /**
93868      * Returns the label for the field. Defaults to simply returning the {@link #fieldLabel} config. Can be
93869      * overridden to provide
93870      * @return {String} The configured field label, or empty string if not defined
93871      */
93872     getFieldLabel: function() {
93873         return this.fieldLabel || '';
93874     },
93875
93876     /**
93877      * @protected
93878      * Generates the arguments for the field decorations {@link #labelableRenderTpl rendering template}.
93879      * @return {Object} The template arguments
93880      */
93881     getLabelableRenderData: function() {
93882         var me = this,
93883             labelAlign = me.labelAlign,
93884             labelCls = me.labelCls,
93885             labelClsExtra = me.labelClsExtra,
93886             labelPad = me.labelPad,
93887             labelStyle;
93888
93889         // Calculate label styles up front rather than in the Field layout for speed; this
93890         // is safe because label alignment/width/pad are not expected to change.
93891         if (labelAlign === 'top') {
93892             labelStyle = 'margin-bottom:' + labelPad + 'px;';
93893         } else {
93894             labelStyle = 'margin-right:' + labelPad + 'px;';
93895             // Add the width for border-box browsers; will be set by the Field layout for content-box
93896             if (Ext.isBorderBox) {
93897                 labelStyle += 'width:' + me.labelWidth + 'px;';
93898             }
93899         }
93900
93901         return Ext.copyTo(
93902             {
93903                 inputId: me.getInputId(),
93904                 fieldLabel: me.getFieldLabel(),
93905                 labelCls: labelClsExtra ? labelCls + ' ' + labelClsExtra : labelCls,
93906                 labelStyle: labelStyle + (me.labelStyle || ''),
93907                 subTplMarkup: me.getSubTplMarkup()
93908             },
93909             me,
93910             'hideLabel,hideEmptyLabel,fieldBodyCls,baseBodyCls,errorMsgCls,clearCls,labelSeparator',
93911             true
93912         );
93913     },
93914
93915     onLabelableRender: function () {
93916         this.addChildEls(
93917             /**
93918              * @property labelEl
93919              * @type Ext.Element
93920              * The label Element for this component. Only available after the component has been rendered.
93921              */
93922             'labelEl',
93923
93924             /**
93925              * @property bodyEl
93926              * @type Ext.Element
93927              * The div Element wrapping the component's contents. Only available after the component has been rendered.
93928              */
93929             'bodyEl',
93930
93931             /**
93932              * @property errorEl
93933              * @type Ext.Element
93934              * The div Element that will contain the component's error message(s). Note that depending on the
93935              * configured {@link #msgTarget}, this element may be hidden in favor of some other form of
93936              * presentation, but will always be present in the DOM for use by assistive technologies.
93937              */
93938             'errorEl'
93939         );
93940     },
93941
93942     /**
93943      * @protected
93944      * Gets the markup to be inserted into the outer template's bodyEl. Defaults to empty string, should
93945      * be implemented by classes including this mixin as needed.
93946      * @return {String} The markup to be inserted
93947      */
93948     getSubTplMarkup: function() {
93949         return '';
93950     },
93951
93952     /**
93953      * Get the input id, if any, for this component. This is used as the "for" attribute on the label element.
93954      * Implementing subclasses may also use this as e.g. the id for their own <tt>input</tt> element.
93955      * @return {String} The input id
93956      */
93957     getInputId: function() {
93958         return '';
93959     },
93960
93961     /**
93962      * Gets the active error message for this component, if any. This does not trigger
93963      * validation on its own, it merely returns any message that the component may already hold.
93964      * @return {String} The active error message on the component; if there is no error, an empty string is returned.
93965      */
93966     getActiveError : function() {
93967         return this.activeError || '';
93968     },
93969
93970     /**
93971      * Tells whether the field currently has an active error message. This does not trigger
93972      * validation on its own, it merely looks for any message that the component may already hold.
93973      * @return {Boolean}
93974      */
93975     hasActiveError: function() {
93976         return !!this.getActiveError();
93977     },
93978
93979     /**
93980      * Sets the active error message to the given string. This replaces the entire error message
93981      * contents with the given string. Also see {@link #setActiveErrors} which accepts an Array of
93982      * messages and formats them according to the {@link #activeErrorsTpl}.
93983      *
93984      * Note that this only updates the error message element's text and attributes, you'll have
93985      * to call doComponentLayout to actually update the field's layout to match. If the field extends
93986      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
93987      *
93988      * @param {String} msg The error message
93989      */
93990     setActiveError: function(msg) {
93991         this.activeError = msg;
93992         this.activeErrors = [msg];
93993         this.renderActiveError();
93994     },
93995
93996     /**
93997      * Gets an Array of any active error messages currently applied to the field. This does not trigger
93998      * validation on its own, it merely returns any messages that the component may already hold.
93999      * @return {String[]} The active error messages on the component; if there are no errors, an empty Array is returned.
94000      */
94001     getActiveErrors: function() {
94002         return this.activeErrors || [];
94003     },
94004
94005     /**
94006      * Set the active error message to an Array of error messages. The messages are formatted into
94007      * a single message string using the {@link #activeErrorsTpl}. Also see {@link #setActiveError}
94008      * which allows setting the entire error contents with a single string.
94009      *
94010      * Note that this only updates the error message element's text and attributes, you'll have
94011      * to call doComponentLayout to actually update the field's layout to match. If the field extends
94012      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
94013      *
94014      * @param {String[]} errors The error messages
94015      */
94016     setActiveErrors: function(errors) {
94017         this.activeErrors = errors;
94018         this.activeError = this.getTpl('activeErrorsTpl').apply({errors: errors});
94019         this.renderActiveError();
94020     },
94021
94022     /**
94023      * Clears the active error message(s).
94024      *
94025      * Note that this only clears the error message element's text and attributes, you'll have
94026      * to call doComponentLayout to actually update the field's layout to match. If the field extends
94027      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#clearInvalid clearInvalid} instead.
94028      */
94029     unsetActiveError: function() {
94030         delete this.activeError;
94031         delete this.activeErrors;
94032         this.renderActiveError();
94033     },
94034
94035     /**
94036      * @private
94037      * Updates the rendered DOM to match the current activeError. This only updates the content and
94038      * attributes, you'll have to call doComponentLayout to actually update the display.
94039      */
94040     renderActiveError: function() {
94041         var me = this,
94042             activeError = me.getActiveError(),
94043             hasError = !!activeError;
94044
94045         if (activeError !== me.lastActiveError) {
94046             me.fireEvent('errorchange', me, activeError);
94047             me.lastActiveError = activeError;
94048         }
94049
94050         if (me.rendered && !me.isDestroyed && !me.preventMark) {
94051             // Add/remove invalid class
94052             me.el[hasError ? 'addCls' : 'removeCls'](me.invalidCls);
94053
94054             // Update the aria-invalid attribute
94055             me.getActionEl().dom.setAttribute('aria-invalid', hasError);
94056
94057             // Update the errorEl with the error message text
94058             me.errorEl.dom.innerHTML = activeError;
94059         }
94060     },
94061
94062     /**
94063      * Applies a set of default configuration values to this Labelable instance. For each of the
94064      * properties in the given object, check if this component hasOwnProperty that config; if not
94065      * then it's inheriting a default value from its prototype and we should apply the default value.
94066      * @param {Object} defaults The defaults to apply to the object.
94067      */
94068     setFieldDefaults: function(defaults) {
94069         var me = this;
94070         Ext.iterate(defaults, function(key, val) {
94071             if (!me.hasOwnProperty(key)) {
94072                 me[key] = val;
94073             }
94074         });
94075     },
94076
94077     /**
94078      * @protected Calculate and return the natural width of the bodyEl. Override to provide custom logic.
94079      * Note for implementors: if at all possible this method should be overridden with a custom implementation
94080      * that can avoid anything that would cause the browser to reflow, e.g. querying offsetWidth.
94081      */
94082     getBodyNaturalWidth: function() {
94083         return this.bodyEl.getWidth();
94084     }
94085
94086 });
94087
94088 /**
94089  * @docauthor Jason Johnston <jason@sencha.com>
94090  *
94091  * This mixin provides a common interface for the logical behavior and state of form fields, including:
94092  *
94093  * - Getter and setter methods for field values
94094  * - Events and methods for tracking value and validity changes
94095  * - Methods for triggering validation
94096  *
94097  * **NOTE**: When implementing custom fields, it is most likely that you will want to extend the {@link Ext.form.field.Base}
94098  * component class rather than using this mixin directly, as BaseField contains additional logic for generating an
94099  * actual DOM complete with {@link Ext.form.Labelable label and error message} display and a form input field,
94100  * plus methods that bind the Field value getters and setters to the input field's value.
94101  *
94102  * If you do want to implement this mixin directly and don't want to extend {@link Ext.form.field.Base}, then
94103  * you will most likely want to override the following methods with custom implementations: {@link #getValue},
94104  * {@link #setValue}, and {@link #getErrors}. Other methods may be overridden as needed but their base
94105  * implementations should be sufficient for common cases. You will also need to make sure that {@link #initField}
94106  * is called during the component's initialization.
94107  */
94108 Ext.define('Ext.form.field.Field', {
94109     /**
94110      * @property {Boolean} isFormField
94111      * Flag denoting that this component is a Field. Always true.
94112      */
94113     isFormField : true,
94114
94115     /**
94116      * @cfg {Object} value
94117      * A value to initialize this field with.
94118      */
94119
94120     /**
94121      * @cfg {String} name
94122      * The name of the field. By default this is used as the parameter name when including the
94123      * {@link #getSubmitData field value} in a {@link Ext.form.Basic#submit form submit()}. To prevent the field from
94124      * being included in the form submit, set {@link #submitValue} to false.
94125      */
94126
94127     /**
94128      * @cfg {Boolean} disabled
94129      * True to disable the field. Disabled Fields will not be {@link Ext.form.Basic#submit submitted}.
94130      */
94131     disabled : false,
94132
94133     /**
94134      * @cfg {Boolean} submitValue
94135      * Setting this to false will prevent the field from being {@link Ext.form.Basic#submit submitted} even when it is
94136      * not disabled.
94137      */
94138     submitValue: true,
94139
94140     /**
94141      * @cfg {Boolean} validateOnChange
94142      * Specifies whether this field should be validated immediately whenever a change in its value is detected.
94143      * If the validation results in a change in the field's validity, a {@link #validitychange} event will be
94144      * fired. This allows the field to show feedback about the validity of its contents immediately as the user is
94145      * typing.
94146      *
94147      * When set to false, feedback will not be immediate. However the form will still be validated before submitting if
94148      * the clientValidation option to {@link Ext.form.Basic#doAction} is enabled, or if the field or form are validated
94149      * manually.
94150      *
94151      * See also {@link Ext.form.field.Base#checkChangeEvents} for controlling how changes to the field's value are
94152      * detected.
94153      */
94154     validateOnChange: true,
94155
94156     /**
94157      * @private
94158      */
94159     suspendCheckChange: 0,
94160
94161     /**
94162      * Initializes this Field mixin on the current instance. Components using this mixin should call this method during
94163      * their own initialization process.
94164      */
94165     initField: function() {
94166         this.addEvents(
94167             /**
94168              * @event change
94169              * Fires when a user-initiated change is detected in the value of the field.
94170              * @param {Ext.form.field.Field} this
94171              * @param {Object} newValue The new value
94172              * @param {Object} oldValue The original value
94173              */
94174             'change',
94175             /**
94176              * @event validitychange
94177              * Fires when a change in the field's validity is detected.
94178              * @param {Ext.form.field.Field} this
94179              * @param {Boolean} isValid Whether or not the field is now valid
94180              */
94181             'validitychange',
94182             /**
94183              * @event dirtychange
94184              * Fires when a change in the field's {@link #isDirty} state is detected.
94185              * @param {Ext.form.field.Field} this
94186              * @param {Boolean} isDirty Whether or not the field is now dirty
94187              */
94188             'dirtychange'
94189         );
94190
94191         this.initValue();
94192     },
94193
94194     /**
94195      * Initializes the field's value based on the initial config.
94196      */
94197     initValue: function() {
94198         var me = this;
94199
94200         /**
94201          * @property {Object} originalValue
94202          * The original value of the field as configured in the {@link #value} configuration, or as loaded by the last
94203          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
94204          */
94205         me.originalValue = me.lastValue = me.value;
94206
94207         // Set the initial value - prevent validation on initial set
94208         me.suspendCheckChange++;
94209         me.setValue(me.value);
94210         me.suspendCheckChange--;
94211     },
94212
94213     /**
94214      * Returns the {@link Ext.form.field.Field#name name} attribute of the field. This is used as the parameter name
94215      * when including the field value in a {@link Ext.form.Basic#submit form submit()}.
94216      * @return {String} name The field {@link Ext.form.field.Field#name name}
94217      */
94218     getName: function() {
94219         return this.name;
94220     },
94221
94222     /**
94223      * Returns the current data value of the field. The type of value returned is particular to the type of the
94224      * particular field (e.g. a Date object for {@link Ext.form.field.Date}).
94225      * @return {Object} value The field value
94226      */
94227     getValue: function() {
94228         return this.value;
94229     },
94230
94231     /**
94232      * Sets a data value into the field and runs the change detection and validation.
94233      * @param {Object} value The value to set
94234      * @return {Ext.form.field.Field} this
94235      */
94236     setValue: function(value) {
94237         var me = this;
94238         me.value = value;
94239         me.checkChange();
94240         return me;
94241     },
94242
94243     /**
94244      * Returns whether two field {@link #getValue values} are logically equal. Field implementations may override this
94245      * to provide custom comparison logic appropriate for the particular field's data type.
94246      * @param {Object} value1 The first value to compare
94247      * @param {Object} value2 The second value to compare
94248      * @return {Boolean} True if the values are equal, false if inequal.
94249      */
94250     isEqual: function(value1, value2) {
94251         return String(value1) === String(value2);
94252     },
94253     
94254     /**
94255      * Returns whether two values are logically equal.
94256      * Similar to {@link #isEqual}, however null or undefined values will be treated as empty strings.
94257      * @private
94258      * @param {Object} value1 The first value to compare
94259      * @param {Object} value2 The second value to compare
94260      * @return {Boolean} True if the values are equal, false if inequal.
94261      */
94262     isEqualAsString: function(value1, value2){
94263         return String(Ext.value(value1, '')) === String(Ext.value(value2, ''));    
94264     },
94265
94266     /**
94267      * Returns the parameter(s) that would be included in a standard form submit for this field. Typically this will be
94268      * an object with a single name-value pair, the name being this field's {@link #getName name} and the value being
94269      * its current stringified value. More advanced field implementations may return more than one name-value pair.
94270      *
94271      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
94272      * validated}.
94273      *
94274      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
94275      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
94276      * submitted.
94277      */
94278     getSubmitData: function() {
94279         var me = this,
94280             data = null;
94281         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
94282             data = {};
94283             data[me.getName()] = '' + me.getValue();
94284         }
94285         return data;
94286     },
94287
94288     /**
94289      * Returns the value(s) that should be saved to the {@link Ext.data.Model} instance for this field, when {@link
94290      * Ext.form.Basic#updateRecord} is called. Typically this will be an object with a single name-value pair, the name
94291      * being this field's {@link #getName name} and the value being its current data value. More advanced field
94292      * implementations may return more than one name-value pair. The returned values will be saved to the corresponding
94293      * field names in the Model.
94294      *
94295      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
94296      * validated}.
94297      *
94298      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
94299      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
94300      * submitted.
94301      */
94302     getModelData: function() {
94303         var me = this,
94304             data = null;
94305         if (!me.disabled && !me.isFileUpload()) {
94306             data = {};
94307             data[me.getName()] = me.getValue();
94308         }
94309         return data;
94310     },
94311
94312     /**
94313      * Resets the current field value to the originally loaded value and clears any validation messages. See {@link
94314      * Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
94315      */
94316     reset : function(){
94317         var me = this;
94318
94319         me.setValue(me.originalValue);
94320         me.clearInvalid();
94321         // delete here so we reset back to the original state
94322         delete me.wasValid;
94323     },
94324
94325     /**
94326      * Resets the field's {@link #originalValue} property so it matches the current {@link #getValue value}. This is
94327      * called by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues} if the form's
94328      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} property is set to true.
94329      */
94330     resetOriginalValue: function() {
94331         this.originalValue = this.getValue();
94332         this.checkDirty();
94333     },
94334
94335     /**
94336      * Checks whether the value of the field has changed since the last time it was checked.
94337      * If the value has changed, it:
94338      *
94339      * 1. Fires the {@link #change change event},
94340      * 2. Performs validation if the {@link #validateOnChange} config is enabled, firing the
94341      *    {@link #validitychange validitychange event} if the validity has changed, and
94342      * 3. Checks the {@link #isDirty dirty state} of the field and fires the {@link #dirtychange dirtychange event}
94343      *    if it has changed.
94344      */
94345     checkChange: function() {
94346         if (!this.suspendCheckChange) {
94347             var me = this,
94348                 newVal = me.getValue(),
94349                 oldVal = me.lastValue;
94350             if (!me.isEqual(newVal, oldVal) && !me.isDestroyed) {
94351                 me.lastValue = newVal;
94352                 me.fireEvent('change', me, newVal, oldVal);
94353                 me.onChange(newVal, oldVal);
94354             }
94355         }
94356     },
94357
94358     /**
94359      * @private
94360      * Called when the field's value changes. Performs validation if the {@link #validateOnChange}
94361      * config is enabled, and invokes the dirty check.
94362      */
94363     onChange: function(newVal, oldVal) {
94364         if (this.validateOnChange) {
94365             this.validate();
94366         }
94367         this.checkDirty();
94368     },
94369
94370     /**
94371      * Returns true if the value of this Field has been changed from its {@link #originalValue}.
94372      * Will always return false if the field is disabled.
94373      *
94374      * Note that if the owning {@link Ext.form.Basic form} was configured with
94375      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} then the {@link #originalValue} is updated when
94376      * the values are loaded by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues}.
94377      * @return {Boolean} True if this field has been changed from its original value (and is not disabled),
94378      * false otherwise.
94379      */
94380     isDirty : function() {
94381         var me = this;
94382         return !me.disabled && !me.isEqual(me.getValue(), me.originalValue);
94383     },
94384
94385     /**
94386      * Checks the {@link #isDirty} state of the field and if it has changed since the last time it was checked,
94387      * fires the {@link #dirtychange} event.
94388      */
94389     checkDirty: function() {
94390         var me = this,
94391             isDirty = me.isDirty();
94392         if (isDirty !== me.wasDirty) {
94393             me.fireEvent('dirtychange', me, isDirty);
94394             me.onDirtyChange(isDirty);
94395             me.wasDirty = isDirty;
94396         }
94397     },
94398
94399     /**
94400      * @private Called when the field's dirty state changes.
94401      * @param {Boolean} isDirty
94402      */
94403     onDirtyChange: Ext.emptyFn,
94404
94405     /**
94406      * Runs this field's validators and returns an array of error messages for any validation failures. This is called
94407      * internally during validation and would not usually need to be used manually.
94408      *
94409      * Each subclass should override or augment the return value to provide their own errors.
94410      *
94411      * @param {Object} value The value to get errors for (defaults to the current field value)
94412      * @return {String[]} All error messages for this field; an empty Array if none.
94413      */
94414     getErrors: function(value) {
94415         return [];
94416     },
94417
94418     /**
94419      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
94420      * value. The {@link #validitychange} event will not be fired; use {@link #validate} instead if you want the event
94421      * to fire. **Note**: {@link #disabled} fields are always treated as valid.
94422      *
94423      * Implementations are encouraged to ensure that this method does not have side-effects such as triggering error
94424      * message display.
94425      *
94426      * @return {Boolean} True if the value is valid, else false
94427      */
94428     isValid : function() {
94429         var me = this;
94430         return me.disabled || Ext.isEmpty(me.getErrors());
94431     },
94432
94433     /**
94434      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
94435      * value, and fires the {@link #validitychange} event if the field's validity has changed since the last validation.
94436      * **Note**: {@link #disabled} fields are always treated as valid.
94437      *
94438      * Custom implementations of this method are allowed to have side-effects such as triggering error message display.
94439      * To validate without side-effects, use {@link #isValid}.
94440      *
94441      * @return {Boolean} True if the value is valid, else false
94442      */
94443     validate : function() {
94444         var me = this,
94445             isValid = me.isValid();
94446         if (isValid !== me.wasValid) {
94447             me.wasValid = isValid;
94448             me.fireEvent('validitychange', me, isValid);
94449         }
94450         return isValid;
94451     },
94452
94453     /**
94454      * A utility for grouping a set of modifications which may trigger value changes into a single transaction, to
94455      * prevent excessive firing of {@link #change} events. This is useful for instance if the field has sub-fields which
94456      * are being updated as a group; you don't want the container field to check its own changed state for each subfield
94457      * change.
94458      * @param {Object} fn A function containing the transaction code
94459      */
94460     batchChanges: function(fn) {
94461         try {
94462             this.suspendCheckChange++;
94463             fn();
94464         } catch(e){
94465             throw e;
94466         } finally {
94467             this.suspendCheckChange--;
94468         }
94469         this.checkChange();
94470     },
94471
94472     /**
94473      * Returns whether this Field is a file upload field; if it returns true, forms will use special techniques for
94474      * {@link Ext.form.Basic#submit submitting the form} via AJAX. See {@link Ext.form.Basic#hasUpload} for details. If
94475      * this returns true, the {@link #extractFileInput} method must also be implemented to return the corresponding file
94476      * input element.
94477      * @return {Boolean}
94478      */
94479     isFileUpload: function() {
94480         return false;
94481     },
94482
94483     /**
94484      * Only relevant if the instance's {@link #isFileUpload} method returns true. Returns a reference to the file input
94485      * DOM element holding the user's selected file. The input will be appended into the submission form and will not be
94486      * returned, so this method should also create a replacement.
94487      * @return {HTMLElement}
94488      */
94489     extractFileInput: function() {
94490         return null;
94491     },
94492
94493     /**
94494      * @method markInvalid
94495      * Associate one or more error messages with this field. Components using this mixin should implement this method to
94496      * update the component's rendering to display the messages.
94497      *
94498      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
94499      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
94500      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
94501      *
94502      * @param {String/String[]} errors The error message(s) for the field.
94503      */
94504     markInvalid: Ext.emptyFn,
94505
94506     /**
94507      * @method clearInvalid
94508      * Clear any invalid styles/messages for this field. Components using this mixin should implement this method to
94509      * update the components rendering to clear any existing messages.
94510      *
94511      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
94512      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
94513      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
94514      */
94515     clearInvalid: Ext.emptyFn
94516
94517 });
94518
94519 /**
94520  * @class Ext.layout.component.field.Field
94521  * @extends Ext.layout.component.Component
94522  * Layout class for components with {@link Ext.form.Labelable field labeling}, handling the sizing and alignment of
94523  * the form control, label, and error message treatment.
94524  * @private
94525  */
94526 Ext.define('Ext.layout.component.field.Field', {
94527
94528     /* Begin Definitions */
94529
94530     alias: ['layout.field'],
94531
94532     extend: 'Ext.layout.component.Component',
94533
94534     uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics'],
94535
94536     /* End Definitions */
94537
94538     type: 'field',
94539
94540     beforeLayout: function(width, height) {
94541         var me = this;
94542         return me.callParent(arguments) || (!me.owner.preventMark && me.activeError !== me.owner.getActiveError());
94543     },
94544
94545     onLayout: function(width, height) {
94546         var me = this,
94547             owner = me.owner,
94548             labelStrategy = me.getLabelStrategy(),
94549             errorStrategy = me.getErrorStrategy(),
94550             isDefined = Ext.isDefined,
94551             isNumber = Ext.isNumber,
94552             lastSize, autoWidth, autoHeight, info, undef;
94553
94554         lastSize = me.lastComponentSize || {};
94555         if (!isDefined(width)) {
94556             width = lastSize.width;
94557             if (width < 0) { //first pass lastComponentSize.width is -Infinity
94558                 width = undef;
94559             }
94560         }
94561         if (!isDefined(height)) {
94562             height = lastSize.height;
94563             if (height < 0) { //first pass lastComponentSize.height is -Infinity
94564                 height = undef;
94565             }
94566         }
94567         autoWidth = !isNumber(width);
94568         autoHeight = !isNumber(height);
94569
94570         info = {
94571             autoWidth: autoWidth,
94572             autoHeight: autoHeight,
94573             width: autoWidth ? owner.getBodyNaturalWidth() : width, //always give a pixel width
94574             height: height,
94575             setOuterWidth: false, //whether the outer el width should be set to the calculated width
94576
94577             // insets for the bodyEl from each side of the component layout area
94578             insets: {
94579                 top: 0,
94580                 right: 0,
94581                 bottom: 0,
94582                 left: 0
94583             }
94584         };
94585
94586         // NOTE the order of calculating insets and setting styles here is very important; we must first
94587         // calculate and set horizontal layout alone, as the horizontal sizing of elements can have an impact
94588         // on the vertical sizes due to wrapping, then calculate and set the vertical layout.
94589
94590         // perform preparation on the label and error (setting css classes, qtips, etc.)
94591         labelStrategy.prepare(owner, info);
94592         errorStrategy.prepare(owner, info);
94593
94594         // calculate the horizontal insets for the label and error
94595         labelStrategy.adjustHorizInsets(owner, info);
94596         errorStrategy.adjustHorizInsets(owner, info);
94597
94598         // set horizontal styles for label and error based on the current insets
94599         labelStrategy.layoutHoriz(owner, info);
94600         errorStrategy.layoutHoriz(owner, info);
94601
94602         // calculate the vertical insets for the label and error
94603         labelStrategy.adjustVertInsets(owner, info);
94604         errorStrategy.adjustVertInsets(owner, info);
94605
94606         // set vertical styles for label and error based on the current insets
94607         labelStrategy.layoutVert(owner, info);
94608         errorStrategy.layoutVert(owner, info);
94609
94610         // perform sizing of the elements based on the final dimensions and insets
94611         if (autoWidth && autoHeight) {
94612             // Don't use setTargetSize if auto-sized, so the calculated size is not reused next time
94613             me.setElementSize(owner.el, (info.setOuterWidth ? info.width : undef), info.height);
94614         } else {
94615             me.setTargetSize((!autoWidth || info.setOuterWidth ? info.width : undef), info.height);
94616         }
94617         me.sizeBody(info);
94618
94619         me.activeError = owner.getActiveError();
94620     },
94621     
94622     onFocus: function(){
94623         this.getErrorStrategy().onFocus(this.owner);    
94624     },
94625
94626
94627     /**
94628      * Perform sizing and alignment of the bodyEl (and children) to match the calculated insets.
94629      */
94630     sizeBody: function(info) {
94631         var me = this,
94632             owner = me.owner,
94633             insets = info.insets,
94634             totalWidth = info.width,
94635             totalHeight = info.height,
94636             width = Ext.isNumber(totalWidth) ? totalWidth - insets.left - insets.right : totalWidth,
94637             height = Ext.isNumber(totalHeight) ? totalHeight - insets.top - insets.bottom : totalHeight;
94638
94639         // size the bodyEl
94640         me.setElementSize(owner.bodyEl, width, height);
94641
94642         // size the bodyEl's inner contents if necessary
94643         me.sizeBodyContents(width, height);
94644     },
94645
94646     /**
94647      * Size the contents of the field body, given the full dimensions of the bodyEl. Does nothing by
94648      * default, subclasses can override to handle their specific contents.
94649      * @param {Number} width The bodyEl width
94650      * @param {Number} height The bodyEl height
94651      */
94652     sizeBodyContents: Ext.emptyFn,
94653
94654
94655     /**
94656      * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
94657      * that is appropriate for the field's {@link Ext.form.Labelable#labelAlign labelAlign} config.
94658      */
94659     getLabelStrategy: function() {
94660         var me = this,
94661             strategies = me.labelStrategies,
94662             labelAlign = me.owner.labelAlign;
94663         return strategies[labelAlign] || strategies.base;
94664     },
94665
94666     /**
94667      * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
94668      * that is appropriate for the field's {@link Ext.form.Labelable#msgTarget msgTarget} config.
94669      */
94670     getErrorStrategy: function() {
94671         var me = this,
94672             owner = me.owner,
94673             strategies = me.errorStrategies,
94674             msgTarget = owner.msgTarget;
94675         return !owner.preventMark && Ext.isString(msgTarget) ?
94676                 (strategies[msgTarget] || strategies.elementId) :
94677                 strategies.none;
94678     },
94679
94680
94681
94682     /**
94683      * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
94684      * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#labelAlign} config.
94685      */
94686     labelStrategies: (function() {
94687         var applyIf = Ext.applyIf,
94688             emptyFn = Ext.emptyFn,
94689             base = {
94690                 prepare: function(owner, info) {
94691                     var cls = owner.labelCls + '-' + owner.labelAlign,
94692                         labelEl = owner.labelEl;
94693                     if (labelEl && !labelEl.hasCls(cls)) {
94694                         labelEl.addCls(cls);
94695                     }
94696                 },
94697                 adjustHorizInsets: emptyFn,
94698                 adjustVertInsets: emptyFn,
94699                 layoutHoriz: emptyFn,
94700                 layoutVert: emptyFn
94701             },
94702             left = applyIf({
94703                 prepare: function(owner, info) {
94704                     base.prepare(owner, info);
94705                     // If auto width, add the label width to the body's natural width.
94706                     if (info.autoWidth) {
94707                         info.width += (!owner.labelEl ? 0 : owner.labelWidth + owner.labelPad);
94708                     }
94709                     // Must set outer width to prevent field from wrapping below floated label
94710                     info.setOuterWidth = true;
94711                 },
94712                 adjustHorizInsets: function(owner, info) {
94713                     if (owner.labelEl) {
94714                         info.insets.left += owner.labelWidth + owner.labelPad;
94715                     }
94716                 },
94717                 layoutHoriz: function(owner, info) {
94718                     // For content-box browsers we can't rely on Labelable.js#getLabelableRenderData
94719                     // setting the width style because it needs to account for the final calculated
94720                     // padding/border styles for the label. So we set the width programmatically here to
94721                     // normalize content-box sizing, while letting border-box browsers use the original
94722                     // width style.
94723                     var labelEl = owner.labelEl;
94724                     if (labelEl && !owner.isLabelSized && !Ext.isBorderBox) {
94725                         labelEl.setWidth(owner.labelWidth);
94726                         owner.isLabelSized = true;
94727                     }
94728                 }
94729             }, base);
94730
94731
94732         return {
94733             base: base,
94734
94735             /**
94736              * Label displayed above the bodyEl
94737              */
94738             top: applyIf({
94739                 adjustVertInsets: function(owner, info) {
94740                     var labelEl = owner.labelEl;
94741                     if (labelEl) {
94742                         info.insets.top += Ext.util.TextMetrics.measure(labelEl, owner.fieldLabel, info.width).height +
94743                                            labelEl.getFrameWidth('tb') + owner.labelPad;
94744                     }
94745                 }
94746             }, base),
94747
94748             /**
94749              * Label displayed to the left of the bodyEl
94750              */
94751             left: left,
94752
94753             /**
94754              * Same as left, only difference is text-align in CSS
94755              */
94756             right: left
94757         };
94758     })(),
94759
94760
94761
94762     /**
94763      * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
94764      * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#msgTarget} config.
94765      */
94766     errorStrategies: (function() {
94767         function setDisplayed(el, displayed) {
94768             var wasDisplayed = el.getStyle('display') !== 'none';
94769             if (displayed !== wasDisplayed) {
94770                 el.setDisplayed(displayed);
94771             }
94772         }
94773
94774         function setStyle(el, name, value) {
94775             if (el.getStyle(name) !== value) {
94776                 el.setStyle(name, value);
94777             }
94778         }
94779         
94780         function showTip(owner) {
94781             var tip = Ext.layout.component.field.Field.tip,
94782                 target;
94783                 
94784             if (tip && tip.isVisible()) {
94785                 target = tip.activeTarget;
94786                 if (target && target.el === owner.getActionEl().dom) {
94787                     tip.toFront(true);
94788                 }
94789             }
94790         }
94791
94792         var applyIf = Ext.applyIf,
94793             emptyFn = Ext.emptyFn,
94794             base = {
94795                 prepare: function(owner) {
94796                     setDisplayed(owner.errorEl, false);
94797                 },
94798                 adjustHorizInsets: emptyFn,
94799                 adjustVertInsets: emptyFn,
94800                 layoutHoriz: emptyFn,
94801                 layoutVert: emptyFn,
94802                 onFocus: emptyFn
94803             };
94804
94805         return {
94806             none: base,
94807
94808             /**
94809              * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
94810              */
94811             side: applyIf({
94812                 prepare: function(owner) {
94813                     var errorEl = owner.errorEl;
94814                     errorEl.addCls(Ext.baseCSSPrefix + 'form-invalid-icon');
94815                     Ext.layout.component.field.Field.initTip();
94816                     errorEl.dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
94817                     setDisplayed(errorEl, owner.hasActiveError());
94818                 },
94819                 adjustHorizInsets: function(owner, info) {
94820                     if (owner.autoFitErrors && owner.hasActiveError()) {
94821                         info.insets.right += owner.errorEl.getWidth();
94822                     }
94823                 },
94824                 layoutHoriz: function(owner, info) {
94825                     if (owner.hasActiveError()) {
94826                         setStyle(owner.errorEl, 'left', info.width - info.insets.right + 'px');
94827                     }
94828                 },
94829                 layoutVert: function(owner, info) {
94830                     if (owner.hasActiveError()) {
94831                         setStyle(owner.errorEl, 'top', info.insets.top + 'px');
94832                     }
94833                 },
94834                 onFocus: showTip
94835             }, base),
94836
94837             /**
94838              * Error message displayed underneath the bodyEl
94839              */
94840             under: applyIf({
94841                 prepare: function(owner) {
94842                     var errorEl = owner.errorEl,
94843                         cls = Ext.baseCSSPrefix + 'form-invalid-under';
94844                     if (!errorEl.hasCls(cls)) {
94845                         errorEl.addCls(cls);
94846                     }
94847                     setDisplayed(errorEl, owner.hasActiveError());
94848                 },
94849                 adjustVertInsets: function(owner, info) {
94850                     if (owner.autoFitErrors) {
94851                         info.insets.bottom += owner.errorEl.getHeight();
94852                     }
94853                 },
94854                 layoutHoriz: function(owner, info) {
94855                     var errorEl = owner.errorEl,
94856                         insets = info.insets;
94857
94858                     setStyle(errorEl, 'width', info.width - insets.right - insets.left + 'px');
94859                     setStyle(errorEl, 'marginLeft', insets.left + 'px');
94860                 }
94861             }, base),
94862
94863             /**
94864              * Error displayed as QuickTip on hover of the field container
94865              */
94866             qtip: applyIf({
94867                 prepare: function(owner) {
94868                     setDisplayed(owner.errorEl, false);
94869                     Ext.layout.component.field.Field.initTip();
94870                     owner.getActionEl().dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
94871                 },
94872                 onFocus: showTip
94873             }, base),
94874
94875             /**
94876              * Error displayed as title tip on hover of the field container
94877              */
94878             title: applyIf({
94879                 prepare: function(owner) {
94880                     setDisplayed(owner.errorEl, false);
94881                     owner.el.dom.title = owner.getActiveError() || '';
94882                 }
94883             }, base),
94884
94885             /**
94886              * Error message displayed as content of an element with a given id elsewhere in the app
94887              */
94888             elementId: applyIf({
94889                 prepare: function(owner) {
94890                     setDisplayed(owner.errorEl, false);
94891                     var targetEl = Ext.fly(owner.msgTarget);
94892                     if (targetEl) {
94893                         targetEl.dom.innerHTML = owner.getActiveError() || '';
94894                         targetEl.setDisplayed(owner.hasActiveError());
94895                     }
94896                 }
94897             }, base)
94898         };
94899     })(),
94900
94901     statics: {
94902         /**
94903          * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
94904          * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
94905          */
94906         initTip: function() {
94907             var tip = this.tip;
94908             if (!tip) {
94909                 tip = this.tip = Ext.create('Ext.tip.QuickTip', {
94910                     baseCls: Ext.baseCSSPrefix + 'form-invalid-tip',
94911                     renderTo: Ext.getBody()
94912                 });
94913                 tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
94914             }
94915         },
94916
94917         /**
94918          * Destroy the error tip instance.
94919          */
94920         destroyTip: function() {
94921             var tip = this.tip;
94922             if (tip) {
94923                 tip.destroy();
94924                 delete this.tip;
94925             }
94926         }
94927     }
94928
94929 });
94930
94931 /**
94932  * @singleton
94933  * @alternateClassName Ext.form.VTypes
94934  *
94935  * This is a singleton object which contains a set of commonly used field validation functions
94936  * and provides a mechanism for creating reusable custom field validations.
94937  * The following field validation functions are provided out of the box:
94938  *
94939  * - {@link #alpha}
94940  * - {@link #alphanum}
94941  * - {@link #email}
94942  * - {@link #url}
94943  *
94944  * VTypes can be applied to a {@link Ext.form.field.Text Text Field} using the `{@link Ext.form.field.Text#vtype vtype}` configuration:
94945  *
94946  *     Ext.create('Ext.form.field.Text', {
94947  *         fieldLabel: 'Email Address',
94948  *         name: 'email',
94949  *         vtype: 'email' // applies email validation rules to this field
94950  *     });
94951  *
94952  * To create custom VTypes:
94953  *
94954  *     // custom Vtype for vtype:'time'
94955  *     var timeTest = /^([1-9]|1[0-9]):([0-5][0-9])(\s[a|p]m)$/i;
94956  *     Ext.apply(Ext.form.field.VTypes, {
94957  *         //  vtype validation function
94958  *         time: function(val, field) {
94959  *             return timeTest.test(val);
94960  *         },
94961  *         // vtype Text property: The error text to display when the validation function returns false
94962  *         timeText: 'Not a valid time.  Must be in the format "12:34 PM".',
94963  *         // vtype Mask property: The keystroke filter mask
94964  *         timeMask: /[\d\s:amp]/i
94965  *     });
94966  *
94967  * In the above example the `time` function is the validator that will run when field validation occurs,
94968  * `timeText` is the error message, and `timeMask` limits what characters can be typed into the field.
94969  * Note that the `Text` and `Mask` functions must begin with the same name as the validator function.
94970  *
94971  * Using a custom validator is the same as using one of the build-in validators - just use the name of the validator function
94972  * as the `{@link Ext.form.field.Text#vtype vtype}` configuration on a {@link Ext.form.field.Text Text Field}:
94973  *
94974  *     Ext.create('Ext.form.field.Text', {
94975  *         fieldLabel: 'Departure Time',
94976  *         name: 'departureTime',
94977  *         vtype: 'time' // applies custom time validation rules to this field
94978  *     });
94979  *
94980  * Another example of a custom validator:
94981  *
94982  *     // custom Vtype for vtype:'IPAddress'
94983  *     Ext.apply(Ext.form.field.VTypes, {
94984  *         IPAddress:  function(v) {
94985  *             return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
94986  *         },
94987  *         IPAddressText: 'Must be a numeric IP address',
94988  *         IPAddressMask: /[\d\.]/i
94989  *     });
94990  *
94991  * It's important to note that using {@link Ext#apply Ext.apply()} means that the custom validator function
94992  * as well as `Text` and `Mask` fields are added as properties of the `Ext.form.field.VTypes` singleton.
94993  */
94994 Ext.define('Ext.form.field.VTypes', (function(){
94995     // closure these in so they are only created once.
94996     var alpha = /^[a-zA-Z_]+$/,
94997         alphanum = /^[a-zA-Z0-9_]+$/,
94998         email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
94999         url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
95000
95001     // All these messages and functions are configurable
95002     return {
95003         singleton: true,
95004         alternateClassName: 'Ext.form.VTypes',
95005
95006         /**
95007          * The function used to validate email addresses. Note that this is a very basic validation - complete
95008          * validation per the email RFC specifications is very complex and beyond the scope of this class, although this
95009          * function can be overridden if a more comprehensive validation scheme is desired. See the validation section
95010          * of the [Wikipedia article on email addresses][1] for additional information. This implementation is intended
95011          * to validate the following emails:
95012          *
95013          * - `barney@example.de`
95014          * - `barney.rubble@example.com`
95015          * - `barney-rubble@example.coop`
95016          * - `barney+rubble@example.com`
95017          *
95018          * [1]: http://en.wikipedia.org/wiki/E-mail_address
95019          *
95020          * @param {String} value The email address
95021          * @return {Boolean} true if the RegExp test passed, and false if not.
95022          */
95023         'email' : function(v){
95024             return email.test(v);
95025         },
95026         /**
95027          * @property {String} emailText
95028          * The error text to display when the email validation function returns false.
95029          * Defaults to: 'This field should be an e-mail address in the format "user@example.com"'
95030          */
95031         'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
95032         /**
95033          * @property {RegExp} emailMask
95034          * The keystroke filter mask to be applied on email input. See the {@link #email} method for information about
95035          * more complex email validation. Defaults to: /[a-z0-9_\.\-@]/i
95036          */
95037         'emailMask' : /[a-z0-9_\.\-@\+]/i,
95038
95039         /**
95040          * The function used to validate URLs
95041          * @param {String} value The URL
95042          * @return {Boolean} true if the RegExp test passed, and false if not.
95043          */
95044         'url' : function(v){
95045             return url.test(v);
95046         },
95047         /**
95048          * @property {String} urlText
95049          * The error text to display when the url validation function returns false.
95050          * Defaults to: 'This field should be a URL in the format "http:/'+'/www.example.com"'
95051          */
95052         'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
95053
95054         /**
95055          * The function used to validate alpha values
95056          * @param {String} value The value
95057          * @return {Boolean} true if the RegExp test passed, and false if not.
95058          */
95059         'alpha' : function(v){
95060             return alpha.test(v);
95061         },
95062         /**
95063          * @property {String} alphaText
95064          * The error text to display when the alpha validation function returns false.
95065          * Defaults to: 'This field should only contain letters and _'
95066          */
95067         'alphaText' : 'This field should only contain letters and _',
95068         /**
95069          * @property {RegExp} alphaMask
95070          * The keystroke filter mask to be applied on alpha input. Defaults to: /[a-z_]/i
95071          */
95072         'alphaMask' : /[a-z_]/i,
95073
95074         /**
95075          * The function used to validate alphanumeric values
95076          * @param {String} value The value
95077          * @return {Boolean} true if the RegExp test passed, and false if not.
95078          */
95079         'alphanum' : function(v){
95080             return alphanum.test(v);
95081         },
95082         /**
95083          * @property {String} alphanumText
95084          * The error text to display when the alphanumeric validation function returns false.
95085          * Defaults to: 'This field should only contain letters, numbers and _'
95086          */
95087         'alphanumText' : 'This field should only contain letters, numbers and _',
95088         /**
95089          * @property {RegExp} alphanumMask
95090          * The keystroke filter mask to be applied on alphanumeric input. Defaults to: /[a-z0-9_]/i
95091          */
95092         'alphanumMask' : /[a-z0-9_]/i
95093     };
95094 })());
95095
95096 /**
95097  * @private
95098  * @class Ext.layout.component.field.Text
95099  * @extends Ext.layout.component.field.Field
95100  * Layout class for {@link Ext.form.field.Text} fields. Handles sizing the input field.
95101  */
95102 Ext.define('Ext.layout.component.field.Text', {
95103     extend: 'Ext.layout.component.field.Field',
95104     alias: 'layout.textfield',
95105     requires: ['Ext.util.TextMetrics'],
95106
95107     type: 'textfield',
95108
95109
95110     /**
95111      * Allow layout to proceed if the {@link Ext.form.field.Text#grow} config is enabled and the value has
95112      * changed since the last layout.
95113      */
95114     beforeLayout: function(width, height) {
95115         var me = this,
95116             owner = me.owner,
95117             lastValue = this.lastValue,
95118             value = owner.getRawValue();
95119         this.lastValue = value;
95120         return me.callParent(arguments) || (owner.grow && value !== lastValue);
95121     },
95122
95123
95124     /**
95125      * Size the field body contents given the total dimensions of the bodyEl, taking into account the optional
95126      * {@link Ext.form.field.Text#grow} configurations.
95127      * @param {Number} width The bodyEl width
95128      * @param {Number} height The bodyEl height
95129      */
95130     sizeBodyContents: function(width, height) {
95131         var size = this.adjustForGrow(width, height);
95132         this.setElementSize(this.owner.inputEl, size[0], size[1]);
95133     },
95134
95135
95136     /**
95137      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
95138      * size based on the text field's {@link Ext.form.field.Text#grow grow config}.
95139      * @param {Number} width The bodyEl width
95140      * @param {Number} height The bodyEl height
95141      * @return {Number[]} [inputElWidth, inputElHeight]
95142      */
95143     adjustForGrow: function(width, height) {
95144         var me = this,
95145             owner = me.owner,
95146             inputEl, value, calcWidth,
95147             result = [width, height];
95148
95149         if (owner.grow) {
95150             inputEl = owner.inputEl;
95151
95152             // Find the width that contains the whole text value
95153             value = (inputEl.dom.value || (owner.hasFocus ? '' : owner.emptyText) || '') + owner.growAppend;
95154             calcWidth = inputEl.getTextWidth(value) + inputEl.getBorderWidth("lr") + inputEl.getPadding("lr");
95155
95156             // Constrain
95157             result[0] = Ext.Number.constrain(calcWidth, owner.growMin,
95158                     Math.max(owner.growMin, Math.min(owner.growMax, Ext.isNumber(width) ? width : Infinity)));
95159         }
95160
95161         return result;
95162     }
95163
95164 });
95165
95166 /**
95167  * @private
95168  * @class Ext.layout.component.field.TextArea
95169  * @extends Ext.layout.component.field.Field
95170  * Layout class for {@link Ext.form.field.TextArea} fields. Handles sizing the textarea field.
95171  */
95172 Ext.define('Ext.layout.component.field.TextArea', {
95173     extend: 'Ext.layout.component.field.Text',
95174     alias: 'layout.textareafield',
95175
95176     type: 'textareafield',
95177
95178
95179     /**
95180      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
95181      * size based on the text field's {@link Ext.form.field.Text#grow grow config}. Overrides the
95182      * textfield layout's implementation to handle height rather than width.
95183      * @param {Number} width The bodyEl width
95184      * @param {Number} height The bodyEl height
95185      * @return {Number[]} [inputElWidth, inputElHeight]
95186      */
95187     adjustForGrow: function(width, height) {
95188         var me = this,
95189             owner = me.owner,
95190             inputEl, value, max,
95191             curWidth, curHeight, calcHeight,
95192             result = [width, height];
95193
95194         if (owner.grow) {
95195             inputEl = owner.inputEl;
95196             curWidth = inputEl.getWidth(true); //subtract border/padding to get the available width for the text
95197             curHeight = inputEl.getHeight();
95198
95199             // Get and normalize the field value for measurement
95200             value = inputEl.dom.value || '&#160;';
95201             value += owner.growAppend;
95202
95203             // Translate newlines to <br> tags
95204             value = value.replace(/\n/g, '<br>');
95205
95206             // Find the height that contains the whole text value
95207             calcHeight = Ext.util.TextMetrics.measure(inputEl, value, curWidth).height +
95208                          inputEl.getBorderWidth("tb") + inputEl.getPadding("tb");
95209
95210             // Constrain
95211             max = owner.growMax;
95212             if (Ext.isNumber(height)) {
95213                 max = Math.min(max, height);
95214             }
95215             result[1] = Ext.Number.constrain(calcHeight, owner.growMin, max);
95216         }
95217
95218         return result;
95219     }
95220
95221 });
95222 /**
95223  * @class Ext.layout.container.Anchor
95224  * @extends Ext.layout.container.Container
95225  * 
95226  * This is a layout that enables anchoring of contained elements relative to the container's dimensions.
95227  * If the container is resized, all anchored items are automatically rerendered according to their
95228  * `{@link #anchor}` rules.
95229  *
95230  * This class is intended to be extended or created via the {@link Ext.container.AbstractContainer#layout layout}: 'anchor' 
95231  * config, and should generally not need to be created directly via the new keyword.
95232  * 
95233  * AnchorLayout does not have any direct config options (other than inherited ones). By default,
95234  * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
95235  * container using the AnchorLayout can supply an anchoring-specific config property of `anchorSize`.
95236  *
95237  * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
95238  * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
95239  * logic if necessary.  
95240  *
95241  *     @example
95242  *     Ext.create('Ext.Panel', {
95243  *         width: 500,
95244  *         height: 400,
95245  *         title: "AnchorLayout Panel",
95246  *         layout: 'anchor',
95247  *         renderTo: Ext.getBody(),
95248  *         items: [
95249  *             {
95250  *                 xtype: 'panel',
95251  *                 title: '75% Width and 20% Height',
95252  *                 anchor: '75% 20%'
95253  *             },
95254  *             {
95255  *                 xtype: 'panel',
95256  *                 title: 'Offset -300 Width & -200 Height',
95257  *                 anchor: '-300 -200'          
95258  *             },
95259  *             {
95260  *                 xtype: 'panel',
95261  *                 title: 'Mixed Offset and Percent',
95262  *                 anchor: '-250 20%'
95263  *             }
95264  *         ]
95265  *     });
95266  */
95267 Ext.define('Ext.layout.container.Anchor', {
95268
95269     /* Begin Definitions */
95270
95271     alias: 'layout.anchor',
95272     extend: 'Ext.layout.container.Container',
95273     alternateClassName: 'Ext.layout.AnchorLayout',
95274
95275     /* End Definitions */
95276
95277     /**
95278      * @cfg {String} anchor
95279      *
95280      * This configuation option is to be applied to **child `items`** of a container managed by
95281      * this layout (ie. configured with `layout:'anchor'`).
95282      *
95283      * This value is what tells the layout how an item should be anchored to the container. `items`
95284      * added to an AnchorLayout accept an anchoring-specific config property of **anchor** which is a string
95285      * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
95286      * The following types of anchor values are supported:
95287      *
95288      * - **Percentage** : Any value between 1 and 100, expressed as a percentage.
95289      *
95290      *   The first anchor is the percentage width that the item should take up within the container, and the
95291      *   second is the percentage height.  For example:
95292      *
95293      *       // two values specified
95294      *       anchor: '100% 50%' // render item complete width of the container and
95295      *                          // 1/2 height of the container
95296      *       // one value specified
95297      *       anchor: '100%'     // the width value; the height will default to auto
95298      *
95299      * - **Offsets** : Any positive or negative integer value.
95300      *
95301      *   This is a raw adjustment where the first anchor is the offset from the right edge of the container,
95302      *   and the second is the offset from the bottom edge. For example:
95303      *
95304      *       // two values specified
95305      *       anchor: '-50 -100' // render item the complete width of the container
95306      *                          // minus 50 pixels and
95307      *                          // the complete height minus 100 pixels.
95308      *       // one value specified
95309      *       anchor: '-50'      // anchor value is assumed to be the right offset value
95310      *                          // bottom offset will default to 0
95311      *
95312      * - **Sides** : Valid values are `right` (or `r`) and `bottom` (or `b`).
95313      *
95314      *   Either the container must have a fixed size or an anchorSize config value defined at render time in
95315      *   order for these to have any effect.
95316      *   
95317      * - **Mixed** :
95318      *
95319      *   Anchor values can also be mixed as needed.  For example, to render the width offset from the container
95320      *   right edge by 50 pixels and 75% of the container's height use:
95321      *   
95322      *       anchor:   '-50 75%'
95323      */
95324     type: 'anchor',
95325
95326     /**
95327      * @cfg {String} defaultAnchor
95328      * 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%'.
95329      */
95330     defaultAnchor: '100%',
95331
95332     parseAnchorRE: /^(r|right|b|bottom)$/i,
95333
95334     // private
95335     onLayout: function() {
95336         this.callParent(arguments);
95337
95338         var me = this,
95339             size = me.getLayoutTargetSize(),
95340             owner = me.owner,
95341             target = me.getTarget(),
95342             ownerWidth = size.width,
95343             ownerHeight = size.height,
95344             overflow = target.getStyle('overflow'),
95345             components = me.getVisibleItems(owner),
95346             len = components.length,
95347             boxes = [],
95348             box, newTargetSize, component, anchorSpec, calcWidth, calcHeight,
95349             i, el, cleaner;
95350
95351         if (ownerWidth < 20 && ownerHeight < 20) {
95352             return;
95353         }
95354
95355         // Anchor layout uses natural HTML flow to arrange the child items.
95356         // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
95357         // containing element height, we create a zero-sized element with style clear:both to force a "new line"
95358         if (!me.clearEl) {
95359             me.clearEl = target.createChild({
95360                 cls: Ext.baseCSSPrefix + 'clear',
95361                 role: 'presentation'
95362             });
95363         }
95364
95365         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
95366         if (!Ext.supports.RightMargin) {
95367             cleaner = Ext.Element.getRightMarginFixCleaner(target);
95368             target.addCls(Ext.baseCSSPrefix + 'inline-children');
95369         }
95370
95371         for (i = 0; i < len; i++) {
95372             component = components[i];
95373             el = component.el;
95374
95375             anchorSpec = component.anchorSpec;
95376             if (anchorSpec) {
95377                 if (anchorSpec.right) {
95378                     calcWidth = me.adjustWidthAnchor(anchorSpec.right(ownerWidth) - el.getMargin('lr'), component);
95379                 } else {
95380                     calcWidth = undefined;
95381                 }
95382                 if (anchorSpec.bottom) {
95383                     calcHeight = me.adjustHeightAnchor(anchorSpec.bottom(ownerHeight) - el.getMargin('tb'), component);
95384                 } else {
95385                     calcHeight = undefined;
95386                 }
95387
95388                 boxes.push({
95389                     component: component,
95390                     anchor: true,
95391                     width: calcWidth || undefined,
95392                     height: calcHeight || undefined
95393                 });
95394             } else {
95395                 boxes.push({
95396                     component: component,
95397                     anchor: false
95398                 });
95399             }
95400         }
95401
95402         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
95403         if (!Ext.supports.RightMargin) {
95404             target.removeCls(Ext.baseCSSPrefix + 'inline-children');
95405             cleaner();
95406         }
95407
95408         for (i = 0; i < len; i++) {
95409             box = boxes[i];
95410             me.setItemSize(box.component, box.width, box.height);
95411         }
95412
95413         if (overflow && overflow != 'hidden' && !me.adjustmentPass) {
95414             newTargetSize = me.getLayoutTargetSize();
95415             if (newTargetSize.width != size.width || newTargetSize.height != size.height) {
95416                 me.adjustmentPass = true;
95417                 me.onLayout();
95418             }
95419         }
95420
95421         delete me.adjustmentPass;
95422     },
95423
95424     // private
95425     parseAnchor: function(a, start, cstart) {
95426         if (a && a != 'none') {
95427             var ratio;
95428             // standard anchor
95429             if (this.parseAnchorRE.test(a)) {
95430                 var diff = cstart - start;
95431                 return function(v) {
95432                     return v - diff;
95433                 };
95434             }    
95435             // percentage
95436             else if (a.indexOf('%') != -1) {
95437                 ratio = parseFloat(a.replace('%', '')) * 0.01;
95438                 return function(v) {
95439                     return Math.floor(v * ratio);
95440                 };
95441             }    
95442             // simple offset adjustment
95443             else {
95444                 a = parseInt(a, 10);
95445                 if (!isNaN(a)) {
95446                     return function(v) {
95447                         return v + a;
95448                     };
95449                 }
95450             }
95451         }
95452         return null;
95453     },
95454
95455     // private
95456     adjustWidthAnchor: function(value, comp) {
95457         return value;
95458     },
95459
95460     // private
95461     adjustHeightAnchor: function(value, comp) {
95462         return value;
95463     },
95464
95465     configureItem: function(item) {
95466         var me = this,
95467             owner = me.owner,
95468             anchor= item.anchor,
95469             anchorsArray,
95470             anchorSpec,
95471             anchorWidth,
95472             anchorHeight;
95473
95474         if (!item.anchor && item.items && !Ext.isNumber(item.width) && !(Ext.isIE6 && Ext.isStrict)) {
95475             item.anchor = anchor = me.defaultAnchor;
95476         }
95477
95478         // find the container anchoring size
95479         if (owner.anchorSize) {
95480             if (typeof owner.anchorSize == 'number') {
95481                 anchorWidth = owner.anchorSize;
95482             }
95483             else {
95484                 anchorWidth = owner.anchorSize.width;
95485                 anchorHeight = owner.anchorSize.height;
95486             }
95487         }
95488         else {
95489             anchorWidth = owner.initialConfig.width;
95490             anchorHeight = owner.initialConfig.height;
95491         }
95492
95493         if (anchor) {
95494             // cache all anchor values
95495             anchorsArray = anchor.split(' ');
95496             item.anchorSpec = anchorSpec = {
95497                 right: me.parseAnchor(anchorsArray[0], item.initialConfig.width, anchorWidth),
95498                 bottom: me.parseAnchor(anchorsArray[1], item.initialConfig.height, anchorHeight)
95499             };
95500
95501             if (anchorSpec.right) {
95502                 item.layoutManagedWidth = 1;
95503             } else {
95504                 item.layoutManagedWidth = 2;
95505             }
95506
95507             if (anchorSpec.bottom) {
95508                 item.layoutManagedHeight = 1;
95509             } else {
95510                 item.layoutManagedHeight = 2;
95511             }
95512         } else {
95513             item.layoutManagedWidth = 2;
95514             item.layoutManagedHeight = 2;
95515         }
95516         this.callParent(arguments);
95517     }
95518
95519 });
95520 /**
95521  * @class Ext.form.action.Load
95522  * @extends Ext.form.action.Action
95523  * <p>A class which handles loading of data from a server into the Fields of an {@link Ext.form.Basic}.</p>
95524  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
95525  * {@link Ext.form.Basic#load load}ing.</p>
95526  * <p><u><b>Response Packet Criteria</b></u></p>
95527  * <p>A response packet <b>must</b> contain:
95528  * <div class="mdetail-params"><ul>
95529  * <li><b><code>success</code></b> property : Boolean</li>
95530  * <li><b><code>data</code></b> property : Object</li>
95531  * <div class="sub-desc">The <code>data</code> property contains the values of Fields to load.
95532  * The individual value object for each Field is passed to the Field's
95533  * {@link Ext.form.field.Field#setValue setValue} method.</div></li>
95534  * </ul></div>
95535  * <p><u><b>JSON Packets</b></u></p>
95536  * <p>By default, response packets are assumed to be JSON, so for the following form load call:<pre><code>
95537 var myFormPanel = new Ext.form.Panel({
95538     title: 'Client and routing info',
95539     items: [{
95540         fieldLabel: 'Client',
95541         name: 'clientName'
95542     }, {
95543         fieldLabel: 'Port of loading',
95544         name: 'portOfLoading'
95545     }, {
95546         fieldLabel: 'Port of discharge',
95547         name: 'portOfDischarge'
95548     }]
95549 });
95550 myFormPanel.{@link Ext.form.Panel#getForm getForm}().{@link Ext.form.Basic#load load}({
95551     url: '/getRoutingInfo.php',
95552     params: {
95553         consignmentRef: myConsignmentRef
95554     },
95555     failure: function(form, action) {
95556         Ext.Msg.alert("Load failed", action.result.errorMessage);
95557     }
95558 });
95559 </code></pre>
95560  * a <b>success response</b> packet may look like this:</p><pre><code>
95561 {
95562     success: true,
95563     data: {
95564         clientName: "Fred. Olsen Lines",
95565         portOfLoading: "FXT",
95566         portOfDischarge: "OSL"
95567     }
95568 }</code></pre>
95569  * while a <b>failure response</b> packet may look like this:</p><pre><code>
95570 {
95571     success: false,
95572     errorMessage: "Consignment reference not found"
95573 }</code></pre>
95574  * <p>Other data may be placed into the response for processing the {@link Ext.form.Basic Form}'s
95575  * callback or event handler methods. The object decoded from this JSON is available in the
95576  * {@link Ext.form.action.Action#result result} property.</p>
95577  */
95578 Ext.define('Ext.form.action.Load', {
95579     extend:'Ext.form.action.Action',
95580     requires: ['Ext.data.Connection'],
95581     alternateClassName: 'Ext.form.Action.Load',
95582     alias: 'formaction.load',
95583
95584     type: 'load',
95585
95586     /**
95587      * @private
95588      */
95589     run: function() {
95590         Ext.Ajax.request(Ext.apply(
95591             this.createCallback(),
95592             {
95593                 method: this.getMethod(),
95594                 url: this.getUrl(),
95595                 headers: this.headers,
95596                 params: this.getParams()
95597             }
95598         ));
95599     },
95600
95601     /**
95602      * @private
95603      */
95604     onSuccess: function(response){
95605         var result = this.processResponse(response),
95606             form = this.form;
95607         if (result === true || !result.success || !result.data) {
95608             this.failureType = Ext.form.action.Action.LOAD_FAILURE;
95609             form.afterAction(this, false);
95610             return;
95611         }
95612         form.clearInvalid();
95613         form.setValues(result.data);
95614         form.afterAction(this, true);
95615     },
95616
95617     /**
95618      * @private
95619      */
95620     handleResponse: function(response) {
95621         var reader = this.form.reader,
95622             rs, data;
95623         if (reader) {
95624             rs = reader.read(response);
95625             data = rs.records && rs.records[0] ? rs.records[0].data : null;
95626             return {
95627                 success : rs.success,
95628                 data : data
95629             };
95630         }
95631         return Ext.decode(response.responseText);
95632     }
95633 });
95634
95635
95636 /**
95637  * A specialized panel intended for use as an application window. Windows are floated, {@link #resizable}, and
95638  * {@link #draggable} by default. Windows can be {@link #maximizable maximized} to fill the viewport, restored to
95639  * their prior size, and can be {@link #minimize}d.
95640  *
95641  * Windows can also be linked to a {@link Ext.ZIndexManager} or managed by the {@link Ext.WindowManager} to provide
95642  * grouping, activation, to front, to back and other application-specific behavior.
95643  *
95644  * By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element specify
95645  * {@link Ext.Component#renderTo renderTo}.
95646  *
95647  * **As with all {@link Ext.container.Container Container}s, it is important to consider how you want the Window to size
95648  * and arrange any child Components. Choose an appropriate {@link #layout} configuration which lays out child Components
95649  * in the required manner.**
95650  *
95651  *     @example
95652  *     Ext.create('Ext.window.Window', {
95653  *         title: 'Hello',
95654  *         height: 200,
95655  *         width: 400,
95656  *         layout: 'fit',
95657  *         items: {  // Let's put an empty grid in just to illustrate fit layout
95658  *             xtype: 'grid',
95659  *             border: false,
95660  *             columns: [{header: 'World'}],                 // One header just for show. There's no data,
95661  *             store: Ext.create('Ext.data.ArrayStore', {}) // A dummy empty data store
95662  *         }
95663  *     }).show();
95664  */
95665 Ext.define('Ext.window.Window', {
95666     extend: 'Ext.panel.Panel',
95667
95668     alternateClassName: 'Ext.Window',
95669
95670     requires: ['Ext.util.ComponentDragger', 'Ext.util.Region', 'Ext.EventManager'],
95671
95672     alias: 'widget.window',
95673
95674     /**
95675      * @cfg {Number} x
95676      * The X position of the left edge of the window on initial showing. Defaults to centering the Window within the
95677      * width of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
95678      */
95679
95680     /**
95681      * @cfg {Number} y
95682      * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within the
95683      * height of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
95684      */
95685
95686     /**
95687      * @cfg {Boolean} [modal=false]
95688      * True to make the window modal and mask everything behind it when displayed, false to display it without
95689      * restricting access to other UI elements.
95690      */
95691
95692     /**
95693      * @cfg {String/Ext.Element} [animateTarget=null]
95694      * Id or element from which the window should animate while opening.
95695      */
95696
95697     /**
95698      * @cfg {String/Number/Ext.Component} defaultFocus
95699      * Specifies a Component to receive focus when this Window is focused.
95700      *
95701      * This may be one of:
95702      *
95703      *   - The index of a footer Button.
95704      *   - The id or {@link Ext.AbstractComponent#itemId} of a descendant Component.
95705      *   - A Component.
95706      */
95707
95708     /**
95709      * @cfg {Function} onEsc
95710      * Allows override of the built-in processing for the escape key. Default action is to close the Window (performing
95711      * whatever action is specified in {@link #closeAction}. To prevent the Window closing when the escape key is
95712      * pressed, specify this as {@link Ext#emptyFn Ext.emptyFn}.
95713      */
95714
95715     /**
95716      * @cfg {Boolean} [collapsed=false]
95717      * True to render the window collapsed, false to render it expanded. Note that if {@link #expandOnShow}
95718      * is true (the default) it will override the `collapsed` config and the window will always be
95719      * expanded when shown.
95720      */
95721
95722     /**
95723      * @cfg {Boolean} [maximized=false]
95724      * True to initially display the window in a maximized state.
95725      */
95726
95727     /**
95728     * @cfg {String} [baseCls='x-window']
95729     * The base CSS class to apply to this panel's element.
95730     */
95731     baseCls: Ext.baseCSSPrefix + 'window',
95732
95733     /**
95734      * @cfg {Boolean/Object} resizable
95735      * Specify as `true` to allow user resizing at each edge and corner of the window, false to disable resizing.
95736      *
95737      * This may also be specified as a config object to Ext.resizer.Resizer
95738      */
95739     resizable: true,
95740
95741     /**
95742      * @cfg {Boolean} draggable
95743      * True to allow the window to be dragged by the header bar, false to disable dragging. Note that
95744      * by default the window will be centered in the viewport, so if dragging is disabled the window may need to be
95745      * positioned programmatically after render (e.g., myWindow.setPosition(100, 100);).
95746      */
95747     draggable: true,
95748
95749     /**
95750      * @cfg {Boolean} constrain
95751      * True to constrain the window within its containing element, false to allow it to fall outside of its containing
95752      * element. By default the window will be rendered to document.body. To render and constrain the window within
95753      * another element specify {@link #renderTo}. Optionally the header only can be constrained
95754      * using {@link #constrainHeader}.
95755      */
95756     constrain: false,
95757
95758     /**
95759      * @cfg {Boolean} constrainHeader
95760      * True to constrain the window header within its containing element (allowing the window body to fall outside of
95761      * its containing element) or false to allow the header to fall outside its containing element.
95762      * Optionally the entire window can be constrained using {@link #constrain}.
95763      */
95764     constrainHeader: false,
95765
95766     /**
95767      * @cfg {Boolean} plain
95768      * True to render the window body with a transparent background so that it will blend into the framing elements,
95769      * false to add a lighter background color to visually highlight the body element and separate it more distinctly
95770      * from the surrounding frame.
95771      */
95772     plain: false,
95773
95774     /**
95775      * @cfg {Boolean} minimizable
95776      * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button
95777      * and disallow minimizing the window. Note that this button provides no implementation -- the
95778      * behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a custom
95779      * minimize behavior implemented for this option to be useful.
95780      */
95781     minimizable: false,
95782
95783     /**
95784      * @cfg {Boolean} maximizable
95785      * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button
95786      * and disallow maximizing the window. Note that when a window is maximized, the tool button
95787      * will automatically change to a 'restore' button with the appropriate behavior already built-in that will restore
95788      * the window to its previous size.
95789      */
95790     maximizable: false,
95791
95792     // inherit docs
95793     minHeight: 100,
95794
95795     // inherit docs
95796     minWidth: 200,
95797
95798     /**
95799      * @cfg {Boolean} expandOnShow
95800      * True to always expand the window when it is displayed, false to keep it in its current state (which may be
95801      * {@link #collapsed}) when displayed.
95802      */
95803     expandOnShow: true,
95804
95805     // inherited docs, same default
95806     collapsible: false,
95807
95808     /**
95809      * @cfg {Boolean} closable
95810      * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
95811      * disallow closing the window.
95812      *
95813      * By default, when close is requested by either clicking the close button in the header or pressing ESC when the
95814      * Window has focus, the {@link #close} method will be called. This will _{@link Ext.Component#destroy destroy}_ the
95815      * Window and its content meaning that it may not be reused.
95816      *
95817      * To make closing a Window _hide_ the Window so that it may be reused, set {@link #closeAction} to 'hide'.
95818      */
95819     closable: true,
95820
95821     /**
95822      * @cfg {Boolean} hidden
95823      * Render this Window hidden. If `true`, the {@link #hide} method will be called internally.
95824      */
95825     hidden: true,
95826
95827     // Inherit docs from Component. Windows render to the body on first show.
95828     autoRender: true,
95829
95830     // Inherit docs from Component. Windows hide using visibility.
95831     hideMode: 'visibility',
95832
95833     /** @cfg {Boolean} floating @hide Windows are always floating*/
95834     floating: true,
95835
95836     ariaRole: 'alertdialog',
95837
95838     itemCls: 'x-window-item',
95839
95840     overlapHeader: true,
95841
95842     ignoreHeaderBorderManagement: true,
95843
95844     // private
95845     initComponent: function() {
95846         var me = this;
95847         me.callParent();
95848         me.addEvents(
95849             /**
95850              * @event activate
95851              * Fires after the window has been visually activated via {@link #setActive}.
95852              * @param {Ext.window.Window} this
95853              */
95854
95855             /**
95856              * @event deactivate
95857              * Fires after the window has been visually deactivated via {@link #setActive}.
95858              * @param {Ext.window.Window} this
95859              */
95860
95861             /**
95862              * @event resize
95863              * Fires after the window has been resized.
95864              * @param {Ext.window.Window} this
95865              * @param {Number} width The window's new width
95866              * @param {Number} height The window's new height
95867              */
95868             'resize',
95869
95870             /**
95871              * @event maximize
95872              * Fires after the window has been maximized.
95873              * @param {Ext.window.Window} this
95874              */
95875             'maximize',
95876
95877             /**
95878              * @event minimize
95879              * Fires after the window has been minimized.
95880              * @param {Ext.window.Window} this
95881              */
95882             'minimize',
95883
95884             /**
95885              * @event restore
95886              * Fires after the window has been restored to its original size after being maximized.
95887              * @param {Ext.window.Window} this
95888              */
95889             'restore'
95890         );
95891
95892         if (me.plain) {
95893             me.addClsWithUI('plain');
95894         }
95895
95896         if (me.modal) {
95897             me.ariaRole = 'dialog';
95898         }
95899     },
95900
95901     // State Management
95902     // private
95903
95904     initStateEvents: function(){
95905         var events = this.stateEvents;
95906         // push on stateEvents if they don't exist
95907         Ext.each(['maximize', 'restore', 'resize', 'dragend'], function(event){
95908             if (Ext.Array.indexOf(events, event)) {
95909                 events.push(event);
95910             }
95911         });
95912         this.callParent();
95913     },
95914
95915     getState: function() {
95916         var me = this,
95917             state = me.callParent() || {},
95918             maximized = !!me.maximized;
95919
95920         state.maximized = maximized;
95921         Ext.apply(state, {
95922             size: maximized ? me.restoreSize : me.getSize(),
95923             pos: maximized ? me.restorePos : me.getPosition()
95924         });
95925         return state;
95926     },
95927
95928     applyState: function(state){
95929         var me = this;
95930
95931         if (state) {
95932             me.maximized = state.maximized;
95933             if (me.maximized) {
95934                 me.hasSavedRestore = true;
95935                 me.restoreSize = state.size;
95936                 me.restorePos = state.pos;
95937             } else {
95938                 Ext.apply(me, {
95939                     width: state.size.width,
95940                     height: state.size.height,
95941                     x: state.pos[0],
95942                     y: state.pos[1]
95943                 });
95944             }
95945         }
95946     },
95947
95948     // private
95949     onMouseDown: function (e) {
95950         var preventFocus;
95951             
95952         if (this.floating) {
95953             if (Ext.fly(e.getTarget()).focusable()) {
95954                 preventFocus = true;
95955             }
95956             this.toFront(preventFocus);
95957         }
95958     },
95959
95960     // private
95961     onRender: function(ct, position) {
95962         var me = this;
95963         me.callParent(arguments);
95964         me.focusEl = me.el;
95965
95966         // Double clicking a header will toggleMaximize
95967         if (me.maximizable) {
95968             me.header.on({
95969                 dblclick: {
95970                     fn: me.toggleMaximize,
95971                     element: 'el',
95972                     scope: me
95973                 }
95974             });
95975         }
95976     },
95977
95978     // private
95979     afterRender: function() {
95980         var me = this,
95981             hidden = me.hidden,
95982             keyMap;
95983
95984         me.hidden = false;
95985         // Component's afterRender sizes and positions the Component
95986         me.callParent();
95987         me.hidden = hidden;
95988
95989         // Create the proxy after the size has been applied in Component.afterRender
95990         me.proxy = me.getProxy();
95991
95992         // clickToRaise
95993         me.mon(me.el, 'mousedown', me.onMouseDown, me);
95994         
95995         // allow the element to be focusable
95996         me.el.set({
95997             tabIndex: -1
95998         });
95999
96000         // Initialize
96001         if (me.maximized) {
96002             me.maximized = false;
96003             me.maximize();
96004         }
96005
96006         if (me.closable) {
96007             keyMap = me.getKeyMap();
96008             keyMap.on(27, me.onEsc, me);
96009
96010             //if (hidden) { ? would be consistent w/before/afterShow...
96011                 keyMap.disable();
96012             //}
96013         }
96014
96015         if (!hidden) {
96016             me.syncMonitorWindowResize();
96017             me.doConstrain();
96018         }
96019     },
96020
96021     /**
96022      * @private
96023      * @override
96024      * Override Component.initDraggable.
96025      * Window uses the header element as the delegate.
96026      */
96027     initDraggable: function() {
96028         var me = this,
96029             ddConfig;
96030
96031         if (!me.header) {
96032             me.updateHeader(true);
96033         }
96034
96035         /*
96036          * Check the header here again. If for whatever reason it wasn't created in
96037          * updateHeader (preventHeader) then we'll just ignore the rest since the
96038          * header acts as the drag handle.
96039          */
96040         if (me.header) {
96041             ddConfig = Ext.applyIf({
96042                 el: me.el,
96043                 delegate: '#' + me.header.id
96044             }, me.draggable);
96045
96046             // Add extra configs if Window is specified to be constrained
96047             if (me.constrain || me.constrainHeader) {
96048                 ddConfig.constrain = me.constrain;
96049                 ddConfig.constrainDelegate = me.constrainHeader;
96050                 ddConfig.constrainTo = me.constrainTo || me.container;
96051             }
96052
96053             /**
96054              * @property {Ext.util.ComponentDragger} dd
96055              * If this Window is configured {@link #draggable}, this property will contain an instance of
96056              * {@link Ext.util.ComponentDragger} (A subclass of {@link Ext.dd.DragTracker DragTracker}) which handles dragging
96057              * the Window's DOM Element, and constraining according to the {@link #constrain} and {@link #constrainHeader} .
96058              *
96059              * This has implementations of `onBeforeStart`, `onDrag` and `onEnd` which perform the dragging action. If
96060              * extra logic is needed at these points, use {@link Ext.Function#createInterceptor createInterceptor} or
96061              * {@link Ext.Function#createSequence createSequence} to augment the existing implementations.
96062              */
96063             me.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
96064             me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']);
96065         }
96066     },
96067
96068     // private
96069     onEsc: function(k, e) {
96070         e.stopEvent();
96071         this[this.closeAction]();
96072     },
96073
96074     // private
96075     beforeDestroy: function() {
96076         var me = this;
96077         if (me.rendered) {
96078             delete this.animateTarget;
96079             me.hide();
96080             Ext.destroy(
96081                 me.keyMap
96082             );
96083         }
96084         me.callParent();
96085     },
96086
96087     /**
96088      * @private
96089      * @override
96090      * Contribute class-specific tools to the header.
96091      * Called by Panel's initTools.
96092      */
96093     addTools: function() {
96094         var me = this;
96095
96096         // Call Panel's initTools
96097         me.callParent();
96098
96099         if (me.minimizable) {
96100             me.addTool({
96101                 type: 'minimize',
96102                 handler: Ext.Function.bind(me.minimize, me, [])
96103             });
96104         }
96105         if (me.maximizable) {
96106             me.addTool({
96107                 type: 'maximize',
96108                 handler: Ext.Function.bind(me.maximize, me, [])
96109             });
96110             me.addTool({
96111                 type: 'restore',
96112                 handler: Ext.Function.bind(me.restore, me, []),
96113                 hidden: true
96114             });
96115         }
96116     },
96117
96118     /**
96119      * Gets the configured default focus item. If a {@link #defaultFocus} is set, it will receive focus, otherwise the
96120      * Container itself will receive focus.
96121      */
96122     getFocusEl: function() {
96123         var me = this,
96124             f = me.focusEl,
96125             defaultComp = me.defaultButton || me.defaultFocus,
96126             t = typeof db,
96127             el,
96128             ct;
96129
96130         if (Ext.isDefined(defaultComp)) {
96131             if (Ext.isNumber(defaultComp)) {
96132                 f = me.query('button')[defaultComp];
96133             } else if (Ext.isString(defaultComp)) {
96134                 f = me.down('#' + defaultComp);
96135             } else {
96136                 f = defaultComp;
96137             }
96138         }
96139         return f || me.focusEl;
96140     },
96141
96142     // private
96143     beforeShow: function() {
96144         this.callParent();
96145
96146         if (this.expandOnShow) {
96147             this.expand(false);
96148         }
96149     },
96150
96151     // private
96152     afterShow: function(animateTarget) {
96153         var me = this,
96154             animating = animateTarget || me.animateTarget;
96155
96156
96157         // No constraining code needs to go here.
96158         // Component.onShow constrains the Component. *If the constrain config is true*
96159
96160         // Perform superclass's afterShow tasks
96161         // Which might include animating a proxy from an animateTarget
96162         me.callParent(arguments);
96163
96164         if (me.maximized) {
96165             me.fitContainer();
96166         }
96167
96168         me.syncMonitorWindowResize();
96169         if (!animating) {
96170             me.doConstrain();
96171         }
96172
96173         if (me.keyMap) {
96174             me.keyMap.enable();
96175         }
96176     },
96177
96178     // private
96179     doClose: function() {
96180         var me = this;
96181
96182         // Being called as callback after going through the hide call below
96183         if (me.hidden) {
96184             me.fireEvent('close', me);
96185             if (me.closeAction == 'destroy') {
96186                 this.destroy();
96187             }
96188         } else {
96189             // close after hiding
96190             me.hide(me.animateTarget, me.doClose, me);
96191         }
96192     },
96193
96194     // private
96195     afterHide: function() {
96196         var me = this;
96197
96198         // No longer subscribe to resizing now that we're hidden
96199         me.syncMonitorWindowResize();
96200
96201         // Turn off keyboard handling once window is hidden
96202         if (me.keyMap) {
96203             me.keyMap.disable();
96204         }
96205
96206         // Perform superclass's afterHide tasks.
96207         me.callParent(arguments);
96208     },
96209
96210     // private
96211     onWindowResize: function() {
96212         if (this.maximized) {
96213             this.fitContainer();
96214         }
96215         this.doConstrain();
96216     },
96217
96218     /**
96219      * Placeholder method for minimizing the window. By default, this method simply fires the {@link #minimize} event
96220      * since the behavior of minimizing a window is application-specific. To implement custom minimize behavior, either
96221      * the minimize event can be handled or this method can be overridden.
96222      * @return {Ext.window.Window} this
96223      */
96224     minimize: function() {
96225         this.fireEvent('minimize', this);
96226         return this;
96227     },
96228
96229     afterCollapse: function() {
96230         var me = this;
96231
96232         if (me.maximizable) {
96233             me.tools.maximize.hide();
96234             me.tools.restore.hide();
96235         }
96236         if (me.resizer) {
96237             me.resizer.disable();
96238         }
96239         me.callParent(arguments);
96240     },
96241
96242     afterExpand: function() {
96243         var me = this;
96244
96245         if (me.maximized) {
96246             me.tools.restore.show();
96247         } else if (me.maximizable) {
96248             me.tools.maximize.show();
96249         }
96250         if (me.resizer) {
96251             me.resizer.enable();
96252         }
96253         me.callParent(arguments);
96254     },
96255
96256     /**
96257      * Fits the window within its current container and automatically replaces the {@link #maximizable 'maximize' tool
96258      * button} with the 'restore' tool button. Also see {@link #toggleMaximize}.
96259      * @return {Ext.window.Window} this
96260      */
96261     maximize: function() {
96262         var me = this;
96263
96264         if (!me.maximized) {
96265             me.expand(false);
96266             if (!me.hasSavedRestore) {
96267                 me.restoreSize = me.getSize();
96268                 me.restorePos = me.getPosition(true);
96269             }
96270             if (me.maximizable) {
96271                 me.tools.maximize.hide();
96272                 me.tools.restore.show();
96273             }
96274             me.maximized = true;
96275             me.el.disableShadow();
96276
96277             if (me.dd) {
96278                 me.dd.disable();
96279             }
96280             if (me.collapseTool) {
96281                 me.collapseTool.hide();
96282             }
96283             me.el.addCls(Ext.baseCSSPrefix + 'window-maximized');
96284             me.container.addCls(Ext.baseCSSPrefix + 'window-maximized-ct');
96285
96286             me.syncMonitorWindowResize();
96287             me.setPosition(0, 0);
96288             me.fitContainer();
96289             me.fireEvent('maximize', me);
96290         }
96291         return me;
96292     },
96293
96294     /**
96295      * Restores a {@link #maximizable maximized} window back to its original size and position prior to being maximized
96296      * and also replaces the 'restore' tool button with the 'maximize' tool button. Also see {@link #toggleMaximize}.
96297      * @return {Ext.window.Window} this
96298      */
96299     restore: function() {
96300         var me = this,
96301             tools = me.tools;
96302
96303         if (me.maximized) {
96304             delete me.hasSavedRestore;
96305             me.removeCls(Ext.baseCSSPrefix + 'window-maximized');
96306
96307             // Toggle tool visibility
96308             if (tools.restore) {
96309                 tools.restore.hide();
96310             }
96311             if (tools.maximize) {
96312                 tools.maximize.show();
96313             }
96314             if (me.collapseTool) {
96315                 me.collapseTool.show();
96316             }
96317
96318             // Restore the position/sizing
96319             me.setPosition(me.restorePos);
96320             me.setSize(me.restoreSize);
96321
96322             // Unset old position/sizing
96323             delete me.restorePos;
96324             delete me.restoreSize;
96325
96326             me.maximized = false;
96327
96328             me.el.enableShadow(true);
96329
96330             // Allow users to drag and drop again
96331             if (me.dd) {
96332                 me.dd.enable();
96333             }
96334
96335             me.container.removeCls(Ext.baseCSSPrefix + 'window-maximized-ct');
96336
96337             me.syncMonitorWindowResize();
96338             me.doConstrain();
96339             me.fireEvent('restore', me);
96340         }
96341         return me;
96342     },
96343
96344     /**
96345      * Synchronizes the presence of our listener for window resize events. This method
96346      * should be called whenever this status might change.
96347      * @private
96348      */
96349     syncMonitorWindowResize: function () {
96350         var me = this,
96351             currentlyMonitoring = me._monitoringResize,
96352             // all the states where we should be listening to window resize:
96353             yes = me.monitorResize || me.constrain || me.constrainHeader || me.maximized,
96354             // all the states where we veto this:
96355             veto = me.hidden || me.destroying || me.isDestroyed;
96356
96357         if (yes && !veto) {
96358             // we should be listening...
96359             if (!currentlyMonitoring) {
96360                 // but we aren't, so set it up
96361                 Ext.EventManager.onWindowResize(me.onWindowResize, me);
96362                 me._monitoringResize = true;
96363             }
96364         } else if (currentlyMonitoring) {
96365             // we should not be listening, but we are, so tear it down
96366             Ext.EventManager.removeResizeListener(me.onWindowResize, me);
96367             me._monitoringResize = false;
96368         }
96369     },
96370
96371     /**
96372      * A shortcut method for toggling between {@link #maximize} and {@link #restore} based on the current maximized
96373      * state of the window.
96374      * @return {Ext.window.Window} this
96375      */
96376     toggleMaximize: function() {
96377         return this[this.maximized ? 'restore': 'maximize']();
96378     }
96379
96380     /**
96381      * @cfg {Boolean} autoWidth @hide
96382      * Absolute positioned element and therefore cannot support autoWidth.
96383      * A width is a required configuration.
96384      **/
96385 });
96386
96387 /**
96388  * @docauthor Jason Johnston <jason@sencha.com>
96389  *
96390  * Base class for form fields that provides default event handling, rendering, and other common functionality
96391  * needed by all form field types. Utilizes the {@link Ext.form.field.Field} mixin for value handling and validation,
96392  * and the {@link Ext.form.Labelable} mixin to provide label and error message display.
96393  *
96394  * In most cases you will want to use a subclass, such as {@link Ext.form.field.Text} or {@link Ext.form.field.Checkbox},
96395  * rather than creating instances of this class directly. However if you are implementing a custom form field,
96396  * using this as the parent class is recommended.
96397  *
96398  * # Values and Conversions
96399  *
96400  * Because BaseField implements the Field mixin, it has a main value that can be initialized with the
96401  * {@link #value} config and manipulated via the {@link #getValue} and {@link #setValue} methods. This main
96402  * value can be one of many data types appropriate to the current field, for instance a {@link Ext.form.field.Date Date}
96403  * field would use a JavaScript Date object as its value type. However, because the field is rendered as a HTML
96404  * input, this value data type can not always be directly used in the rendered field.
96405  *
96406  * Therefore BaseField introduces the concept of a "raw value". This is the value of the rendered HTML input field,
96407  * and is normally a String. The {@link #getRawValue} and {@link #setRawValue} methods can be used to directly
96408  * work with the raw value, though it is recommended to use getValue and setValue in most cases.
96409  *
96410  * Conversion back and forth between the main value and the raw value is handled by the {@link #valueToRaw} and
96411  * {@link #rawToValue} methods. If you are implementing a subclass that uses a non-String value data type, you
96412  * should override these methods to handle the conversion.
96413  *
96414  * # Rendering
96415  *
96416  * The content of the field body is defined by the {@link #fieldSubTpl} XTemplate, with its argument data
96417  * created by the {@link #getSubTplData} method. Override this template and/or method to create custom
96418  * field renderings.
96419  *
96420  * # Example usage:
96421  *
96422  *     @example
96423  *     // A simple subclass of BaseField that creates a HTML5 search field. Redirects to the
96424  *     // searchUrl when the Enter key is pressed.222
96425  *     Ext.define('Ext.form.SearchField', {
96426  *         extend: 'Ext.form.field.Base',
96427  *         alias: 'widget.searchfield',
96428  *     
96429  *         inputType: 'search',
96430  *     
96431  *         // Config defining the search URL
96432  *         searchUrl: 'http://www.google.com/search?q={0}',
96433  *     
96434  *         // Add specialkey listener
96435  *         initComponent: function() {
96436  *             this.callParent();
96437  *             this.on('specialkey', this.checkEnterKey, this);
96438  *         },
96439  *     
96440  *         // Handle enter key presses, execute the search if the field has a value
96441  *         checkEnterKey: function(field, e) {
96442  *             var value = this.getValue();
96443  *             if (e.getKey() === e.ENTER && !Ext.isEmpty(value)) {
96444  *                 location.href = Ext.String.format(this.searchUrl, value);
96445  *             }
96446  *         }
96447  *     });
96448  *     
96449  *     Ext.create('Ext.form.Panel', {
96450  *         title: 'BaseField Example',
96451  *         bodyPadding: 5,
96452  *         width: 250,
96453  *     
96454  *         // Fields will be arranged vertically, stretched to full width
96455  *         layout: 'anchor',
96456  *         defaults: {
96457  *             anchor: '100%'
96458  *         },
96459  *         items: [{
96460  *             xtype: 'searchfield',
96461  *             fieldLabel: 'Search',
96462  *             name: 'query'
96463  *         }],
96464  *         renderTo: Ext.getBody()
96465  *     });
96466  */
96467 Ext.define('Ext.form.field.Base', {
96468     extend: 'Ext.Component',
96469     mixins: {
96470         labelable: 'Ext.form.Labelable',
96471         field: 'Ext.form.field.Field'
96472     },
96473     alias: 'widget.field',
96474     alternateClassName: ['Ext.form.Field', 'Ext.form.BaseField'],
96475     requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.field.Field'],
96476
96477     /**
96478      * @cfg {Ext.XTemplate} fieldSubTpl
96479      * The content of the field body is defined by this config option.
96480      */
96481     fieldSubTpl: [ // note: {id} here is really {inputId}, but {cmpId} is available
96482         '<input id="{id}" type="{type}" ',
96483         '<tpl if="name">name="{name}" </tpl>',
96484         '<tpl if="size">size="{size}" </tpl>',
96485         '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
96486         'class="{fieldCls} {typeCls}" autocomplete="off" />',
96487         {
96488             compiled: true,
96489             disableFormats: true
96490         }
96491     ],
96492
96493     /**
96494      * @cfg {String} name
96495      * The name of the field. This is used as the parameter name when including the field value
96496      * in a {@link Ext.form.Basic#submit form submit()}. If no name is configured, it falls back to the {@link #inputId}.
96497      * To prevent the field from being included in the form submit, set {@link #submitValue} to false.
96498      */
96499
96500     /**
96501      * @cfg {String} inputType
96502      * The type attribute for input fields -- e.g. radio, text, password, file. The extended types
96503      * supported by HTML5 inputs (url, email, etc.) may also be used, though using them will cause older browsers to
96504      * fall back to 'text'.
96505      *
96506      * The type 'password' must be used to render that field type currently -- there is no separate Ext component for
96507      * that. You can use {@link Ext.form.field.File} which creates a custom-rendered file upload field, but if you want
96508      * a plain unstyled file input you can use a BaseField with inputType:'file'.
96509      */
96510     inputType: 'text',
96511
96512     /**
96513      * @cfg {Number} tabIndex
96514      * The tabIndex for this field. Note this only applies to fields that are rendered, not those which are built via
96515      * applyTo
96516      */
96517
96518     /**
96519      * @cfg {String} invalidText
96520      * The error text to use when marking a field invalid and no message is provided
96521      */
96522     invalidText : 'The value in this field is invalid',
96523
96524     /**
96525      * @cfg {String} [fieldCls='x-form-field']
96526      * The default CSS class for the field input
96527      */
96528     fieldCls : Ext.baseCSSPrefix + 'form-field',
96529
96530     /**
96531      * @cfg {String} fieldStyle
96532      * Optional CSS style(s) to be applied to the {@link #inputEl field input element}. Should be a valid argument to
96533      * {@link Ext.Element#applyStyles}. Defaults to undefined. See also the {@link #setFieldStyle} method for changing
96534      * the style after initialization.
96535      */
96536
96537     /**
96538      * @cfg {String} [focusCls='x-form-focus']
96539      * The CSS class to use when the field receives focus
96540      */
96541     focusCls : Ext.baseCSSPrefix + 'form-focus',
96542
96543     /**
96544      * @cfg {String} dirtyCls
96545      * The CSS class to use when the field value {@link #isDirty is dirty}.
96546      */
96547     dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
96548
96549     /**
96550      * @cfg {String[]} checkChangeEvents
96551      * A list of event names that will be listened for on the field's {@link #inputEl input element}, which will cause
96552      * the field's value to be checked for changes. If a change is detected, the {@link #change change event} will be
96553      * fired, followed by validation if the {@link #validateOnChange} option is enabled.
96554      *
96555      * Defaults to ['change', 'propertychange'] in Internet Explorer, and ['change', 'input', 'textInput', 'keyup',
96556      * 'dragdrop'] in other browsers. This catches all the ways that field values can be changed in most supported
96557      * browsers; the only known exceptions at the time of writing are:
96558      *
96559      *   - Safari 3.2 and older: cut/paste in textareas via the context menu, and dragging text into textareas
96560      *   - Opera 10 and 11: dragging text into text fields and textareas, and cut via the context menu in text fields
96561      *     and textareas
96562      *   - Opera 9: Same as Opera 10 and 11, plus paste from context menu in text fields and textareas
96563      *
96564      * If you need to guarantee on-the-fly change notifications including these edge cases, you can call the
96565      * {@link #checkChange} method on a repeating interval, e.g. using {@link Ext.TaskManager}, or if the field is within
96566      * a {@link Ext.form.Panel}, you can use the FormPanel's {@link Ext.form.Panel#pollForChanges} configuration to set up
96567      * such a task automatically.
96568      */
96569     checkChangeEvents: Ext.isIE && (!document.documentMode || document.documentMode < 9) ?
96570                         ['change', 'propertychange'] :
96571                         ['change', 'input', 'textInput', 'keyup', 'dragdrop'],
96572
96573     /**
96574      * @cfg {Number} checkChangeBuffer
96575      * Defines a timeout in milliseconds for buffering {@link #checkChangeEvents} that fire in rapid succession.
96576      * Defaults to 50 milliseconds.
96577      */
96578     checkChangeBuffer: 50,
96579
96580     componentLayout: 'field',
96581
96582     /**
96583      * @cfg {Boolean} readOnly
96584      * true to mark the field as readOnly in HTML.
96585      *
96586      * **Note**: this only sets the element's readOnly DOM attribute. Setting `readOnly=true`, for example, will not
96587      * disable triggering a ComboBox or Date; it gives you the option of forcing the user to choose via the trigger
96588      * without typing in the text box. To hide the trigger use `{@link Ext.form.field.Trigger#hideTrigger hideTrigger}`.
96589      */
96590     readOnly : false,
96591
96592     /**
96593      * @cfg {String} readOnlyCls
96594      * The CSS class applied to the component's main element when it is {@link #readOnly}.
96595      */
96596     readOnlyCls: Ext.baseCSSPrefix + 'form-readonly',
96597
96598     /**
96599      * @cfg {String} inputId
96600      * The id that will be given to the generated input DOM element. Defaults to an automatically generated id. If you
96601      * configure this manually, you must make sure it is unique in the document.
96602      */
96603
96604     /**
96605      * @cfg {Boolean} validateOnBlur
96606      * Whether the field should validate when it loses focus. This will cause fields to be validated
96607      * as the user steps through the fields in the form regardless of whether they are making changes to those fields
96608      * along the way. See also {@link #validateOnChange}.
96609      */
96610     validateOnBlur: true,
96611
96612     // private
96613     hasFocus : false,
96614
96615     baseCls: Ext.baseCSSPrefix + 'field',
96616
96617     maskOnDisable: false,
96618
96619     // private
96620     initComponent : function() {
96621         var me = this;
96622
96623         me.callParent();
96624
96625         me.subTplData = me.subTplData || {};
96626
96627         me.addEvents(
96628             /**
96629              * @event focus
96630              * Fires when this field receives input focus.
96631              * @param {Ext.form.field.Base} this
96632              */
96633             'focus',
96634             /**
96635              * @event blur
96636              * Fires when this field loses input focus.
96637              * @param {Ext.form.field.Base} this
96638              */
96639             'blur',
96640             /**
96641              * @event specialkey
96642              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. To handle other keys
96643              * see {@link Ext.util.KeyMap}. You can check {@link Ext.EventObject#getKey} to determine which key was
96644              * pressed. For example:
96645              *
96646              *     var form = new Ext.form.Panel({
96647              *         ...
96648              *         items: [{
96649              *                 fieldLabel: 'Field 1',
96650              *                 name: 'field1',
96651              *                 allowBlank: false
96652              *             },{
96653              *                 fieldLabel: 'Field 2',
96654              *                 name: 'field2',
96655              *                 listeners: {
96656              *                     specialkey: function(field, e){
96657              *                         // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
96658              *                         // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
96659              *                         if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
96660              *                             var form = field.up('form').getForm();
96661              *                             form.submit();
96662              *                         }
96663              *                     }
96664              *                 }
96665              *             }
96666              *         ],
96667              *         ...
96668              *     });
96669              *
96670              * @param {Ext.form.field.Base} this
96671              * @param {Ext.EventObject} e The event object
96672              */
96673             'specialkey'
96674         );
96675
96676         // Init mixins
96677         me.initLabelable();
96678         me.initField();
96679
96680         // Default name to inputId
96681         if (!me.name) {
96682             me.name = me.getInputId();
96683         }
96684     },
96685
96686     /**
96687      * Returns the input id for this field. If none was specified via the {@link #inputId} config, then an id will be
96688      * automatically generated.
96689      */
96690     getInputId: function() {
96691         return this.inputId || (this.inputId = Ext.id());
96692     },
96693
96694     /**
96695      * Creates and returns the data object to be used when rendering the {@link #fieldSubTpl}.
96696      * @return {Object} The template data
96697      * @template
96698      */
96699     getSubTplData: function() {
96700         var me = this,
96701             type = me.inputType,
96702             inputId = me.getInputId();
96703
96704         return Ext.applyIf(me.subTplData, {
96705             id: inputId,
96706             cmpId: me.id,
96707             name: me.name || inputId,
96708             type: type,
96709             size: me.size || 20,
96710             cls: me.cls,
96711             fieldCls: me.fieldCls,
96712             tabIdx: me.tabIndex,
96713             typeCls: Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
96714         });
96715     },
96716
96717     afterRender: function() {
96718         this.callParent();
96719         
96720         if (this.inputEl) {
96721             this.inputEl.selectable();
96722         }
96723     },
96724
96725     /**
96726      * Gets the markup to be inserted into the outer template's bodyEl. For fields this is the actual input element.
96727      */
96728     getSubTplMarkup: function() {
96729         return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
96730     },
96731
96732     initRenderTpl: function() {
96733         var me = this;
96734         if (!me.hasOwnProperty('renderTpl')) {
96735             me.renderTpl = me.getTpl('labelableRenderTpl');
96736         }
96737         return me.callParent();
96738     },
96739
96740     initRenderData: function() {
96741         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
96742     },
96743
96744     /**
96745      * Set the {@link #fieldStyle CSS style} of the {@link #inputEl field input element}.
96746      * @param {String/Object/Function} style The style(s) to apply. Should be a valid argument to {@link
96747      * Ext.Element#applyStyles}.
96748      */
96749     setFieldStyle: function(style) {
96750         var me = this,
96751             inputEl = me.inputEl;
96752         if (inputEl) {
96753             inputEl.applyStyles(style);
96754         }
96755         me.fieldStyle = style;
96756     },
96757
96758     // private
96759     onRender : function() {
96760         var me = this,
96761             fieldStyle = me.fieldStyle;
96762
96763         me.onLabelableRender();
96764
96765         /**
96766          * @property {Ext.Element} inputEl
96767          * The input Element for this Field. Only available after the field has been rendered.
96768          */
96769         me.addChildEls({ name: 'inputEl', id: me.getInputId() });
96770
96771         me.callParent(arguments);
96772
96773         // Make the stored rawValue get set as the input element's value
96774         me.setRawValue(me.rawValue);
96775
96776         if (me.readOnly) {
96777             me.setReadOnly(true);
96778         }
96779         if (me.disabled) {
96780             me.disable();
96781         }
96782         if (fieldStyle) {
96783             me.setFieldStyle(fieldStyle);
96784         }
96785
96786         me.renderActiveError();
96787     },
96788
96789     initAria: function() {
96790         var me = this;
96791         me.callParent();
96792
96793         // Associate the field to the error message element
96794         me.getActionEl().dom.setAttribute('aria-describedby', Ext.id(me.errorEl));
96795     },
96796
96797     getFocusEl: function() {
96798         return this.inputEl;
96799     },
96800
96801     isFileUpload: function() {
96802         return this.inputType === 'file';
96803     },
96804
96805     extractFileInput: function() {
96806         var me = this,
96807             fileInput = me.isFileUpload() ? me.inputEl.dom : null,
96808             clone;
96809         if (fileInput) {
96810             clone = fileInput.cloneNode(true);
96811             fileInput.parentNode.replaceChild(clone, fileInput);
96812             me.inputEl = Ext.get(clone);
96813         }
96814         return fileInput;
96815     },
96816
96817     // private override to use getSubmitValue() as a convenience
96818     getSubmitData: function() {
96819         var me = this,
96820             data = null,
96821             val;
96822         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
96823             val = me.getSubmitValue();
96824             if (val !== null) {
96825                 data = {};
96826                 data[me.getName()] = val;
96827             }
96828         }
96829         return data;
96830     },
96831
96832     /**
96833      * Returns the value that would be included in a standard form submit for this field. This will be combined with the
96834      * field's name to form a name=value pair in the {@link #getSubmitData submitted parameters}. If an empty string is
96835      * returned then just the name= will be submitted; if null is returned then nothing will be submitted.
96836      *
96837      * Note that the value returned will have been {@link #processRawValue processed} but may or may not have been
96838      * successfully {@link #validate validated}.
96839      *
96840      * @return {String} The value to be submitted, or null.
96841      */
96842     getSubmitValue: function() {
96843         return this.processRawValue(this.getRawValue());
96844     },
96845
96846     /**
96847      * Returns the raw value of the field, without performing any normalization, conversion, or validation. To get a
96848      * normalized and converted value see {@link #getValue}.
96849      * @return {String} value The raw String value of the field
96850      */
96851     getRawValue: function() {
96852         var me = this,
96853             v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
96854         me.rawValue = v;
96855         return v;
96856     },
96857
96858     /**
96859      * Sets the field's raw value directly, bypassing {@link #valueToRaw value conversion}, change detection, and
96860      * validation. To set the value with these additional inspections see {@link #setValue}.
96861      * @param {Object} value The value to set
96862      * @return {Object} value The field value that is set
96863      */
96864     setRawValue: function(value) {
96865         var me = this;
96866         value = Ext.value(value, '');
96867         me.rawValue = value;
96868
96869         // Some Field subclasses may not render an inputEl
96870         if (me.inputEl) {
96871             me.inputEl.dom.value = value;
96872         }
96873         return value;
96874     },
96875
96876     /**
96877      * Converts a mixed-type value to a raw representation suitable for displaying in the field. This allows controlling
96878      * how value objects passed to {@link #setValue} are shown to the user, including localization. For instance, for a
96879      * {@link Ext.form.field.Date}, this would control how a Date object passed to {@link #setValue} would be converted
96880      * to a String for display in the field.
96881      *
96882      * See {@link #rawToValue} for the opposite conversion.
96883      *
96884      * The base implementation simply does a standard toString conversion, and converts {@link Ext#isEmpty empty values}
96885      * to an empty string.
96886      *
96887      * @param {Object} value The mixed-type value to convert to the raw representation.
96888      * @return {Object} The converted raw value.
96889      */
96890     valueToRaw: function(value) {
96891         return '' + Ext.value(value, '');
96892     },
96893
96894     /**
96895      * Converts a raw input field value into a mixed-type value that is suitable for this particular field type. This
96896      * allows controlling the normalization and conversion of user-entered values into field-type-appropriate values,
96897      * e.g. a Date object for {@link Ext.form.field.Date}, and is invoked by {@link #getValue}.
96898      *
96899      * It is up to individual implementations to decide how to handle raw values that cannot be successfully converted
96900      * to the desired object type.
96901      *
96902      * See {@link #valueToRaw} for the opposite conversion.
96903      *
96904      * The base implementation does no conversion, returning the raw value untouched.
96905      *
96906      * @param {Object} rawValue
96907      * @return {Object} The converted value.
96908      */
96909     rawToValue: function(rawValue) {
96910         return rawValue;
96911     },
96912
96913     /**
96914      * Performs any necessary manipulation of a raw field value to prepare it for {@link #rawToValue conversion} and/or
96915      * {@link #validate validation}, for instance stripping out ignored characters. In the base implementation it does
96916      * nothing; individual subclasses may override this as needed.
96917      *
96918      * @param {Object} value The unprocessed string value
96919      * @return {Object} The processed string value
96920      */
96921     processRawValue: function(value) {
96922         return value;
96923     },
96924
96925     /**
96926      * Returns the current data value of the field. The type of value returned is particular to the type of the
96927      * particular field (e.g. a Date object for {@link Ext.form.field.Date}), as the result of calling {@link #rawToValue} on
96928      * the field's {@link #processRawValue processed} String value. To return the raw String value, see {@link #getRawValue}.
96929      * @return {Object} value The field value
96930      */
96931     getValue: function() {
96932         var me = this,
96933             val = me.rawToValue(me.processRawValue(me.getRawValue()));
96934         me.value = val;
96935         return val;
96936     },
96937
96938     /**
96939      * Sets a data value into the field and runs the change detection and validation. To set the value directly
96940      * without these inspections see {@link #setRawValue}.
96941      * @param {Object} value The value to set
96942      * @return {Ext.form.field.Field} this
96943      */
96944     setValue: function(value) {
96945         var me = this;
96946         me.setRawValue(me.valueToRaw(value));
96947         return me.mixins.field.setValue.call(me, value);
96948     },
96949
96950
96951     //private
96952     onDisable: function() {
96953         var me = this,
96954             inputEl = me.inputEl;
96955         me.callParent();
96956         if (inputEl) {
96957             inputEl.dom.disabled = true;
96958         }
96959     },
96960
96961     //private
96962     onEnable: function() {
96963         var me = this,
96964             inputEl = me.inputEl;
96965         me.callParent();
96966         if (inputEl) {
96967             inputEl.dom.disabled = false;
96968         }
96969     },
96970
96971     /**
96972      * Sets the read only state of this field.
96973      * @param {Boolean} readOnly Whether the field should be read only.
96974      */
96975     setReadOnly: function(readOnly) {
96976         var me = this,
96977             inputEl = me.inputEl;
96978         if (inputEl) {
96979             inputEl.dom.readOnly = readOnly;
96980             inputEl.dom.setAttribute('aria-readonly', readOnly);
96981         }
96982         me[readOnly ? 'addCls' : 'removeCls'](me.readOnlyCls);
96983         me.readOnly = readOnly;
96984     },
96985
96986     // private
96987     fireKey: function(e){
96988         if(e.isSpecialKey()){
96989             this.fireEvent('specialkey', this, Ext.create('Ext.EventObjectImpl', e));
96990         }
96991     },
96992
96993     // private
96994     initEvents : function(){
96995         var me = this,
96996             inputEl = me.inputEl,
96997             onChangeTask,
96998             onChangeEvent;
96999         if (inputEl) {
97000             me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey,  me);
97001             me.mon(inputEl, 'focus', me.onFocus, me);
97002
97003             // standardise buffer across all browsers + OS-es for consistent event order.
97004             // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
97005             me.mon(inputEl, 'blur', me.onBlur, me, me.inEditor ? {buffer:10} : null);
97006
97007             // listen for immediate value changes
97008             onChangeTask = Ext.create('Ext.util.DelayedTask', me.checkChange, me);
97009             me.onChangeEvent = onChangeEvent = function() {
97010                 onChangeTask.delay(me.checkChangeBuffer);
97011             };
97012             Ext.each(me.checkChangeEvents, function(eventName) {
97013                 if (eventName === 'propertychange') {
97014                     me.usesPropertychange = true;
97015                 }
97016                 me.mon(inputEl, eventName, onChangeEvent);
97017             }, me);
97018         }
97019         me.callParent();
97020     },
97021
97022     doComponentLayout: function() {
97023         var me = this,
97024             inputEl = me.inputEl,
97025             usesPropertychange = me.usesPropertychange,
97026             ename = 'propertychange',
97027             onChangeEvent = me.onChangeEvent;
97028
97029         // In IE if propertychange is one of the checkChangeEvents, we need to remove
97030         // the listener prior to layout and re-add it after, to prevent it from firing
97031         // needlessly for attribute and style changes applied to the inputEl.
97032         if (usesPropertychange) {
97033             me.mun(inputEl, ename, onChangeEvent);
97034         }
97035         me.callParent(arguments);
97036         if (usesPropertychange) {
97037             me.mon(inputEl, ename, onChangeEvent);
97038         }
97039     },
97040
97041     // private
97042     preFocus: Ext.emptyFn,
97043
97044     // private
97045     onFocus: function() {
97046         var me = this,
97047             focusCls = me.focusCls,
97048             inputEl = me.inputEl;
97049         me.preFocus();
97050         if (focusCls && inputEl) {
97051             inputEl.addCls(focusCls);
97052         }
97053         if (!me.hasFocus) {
97054             me.hasFocus = true;
97055             me.componentLayout.onFocus();
97056             me.fireEvent('focus', me);
97057         }
97058     },
97059
97060     // private
97061     beforeBlur : Ext.emptyFn,
97062
97063     // private
97064     onBlur : function(){
97065         var me = this,
97066             focusCls = me.focusCls,
97067             inputEl = me.inputEl;
97068
97069         if (me.destroying) {
97070             return;
97071         }
97072
97073         me.beforeBlur();
97074         if (focusCls && inputEl) {
97075             inputEl.removeCls(focusCls);
97076         }
97077         if (me.validateOnBlur) {
97078             me.validate();
97079         }
97080         me.hasFocus = false;
97081         me.fireEvent('blur', me);
97082         me.postBlur();
97083     },
97084
97085     // private
97086     postBlur : Ext.emptyFn,
97087
97088
97089     /**
97090      * @private Called when the field's dirty state changes. Adds/removes the {@link #dirtyCls} on the main element.
97091      * @param {Boolean} isDirty
97092      */
97093     onDirtyChange: function(isDirty) {
97094         this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
97095     },
97096
97097
97098     /**
97099      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the
97100      * {@link #processRawValue processed raw value} of the field. **Note**: {@link #disabled} fields are
97101      * always treated as valid.
97102      *
97103      * @return {Boolean} True if the value is valid, else false
97104      */
97105     isValid : function() {
97106         var me = this;
97107         return me.disabled || me.validateValue(me.processRawValue(me.getRawValue()));
97108     },
97109
97110
97111     /**
97112      * Uses {@link #getErrors} to build an array of validation errors. If any errors are found, they are passed to
97113      * {@link #markInvalid} and false is returned, otherwise true is returned.
97114      *
97115      * Previously, subclasses were invited to provide an implementation of this to process validations - from 3.2
97116      * onwards {@link #getErrors} should be overridden instead.
97117      *
97118      * @param {Object} value The value to validate
97119      * @return {Boolean} True if all validations passed, false if one or more failed
97120      */
97121     validateValue: function(value) {
97122         var me = this,
97123             errors = me.getErrors(value),
97124             isValid = Ext.isEmpty(errors);
97125         if (!me.preventMark) {
97126             if (isValid) {
97127                 me.clearInvalid();
97128             } else {
97129                 me.markInvalid(errors);
97130             }
97131         }
97132
97133         return isValid;
97134     },
97135
97136     /**
97137      * Display one or more error messages associated with this field, using {@link #msgTarget} to determine how to
97138      * display the messages and applying {@link #invalidCls} to the field's UI element.
97139      *
97140      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
97141      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
97142      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
97143      *
97144      * @param {String/String[]} errors The validation message(s) to display.
97145      */
97146     markInvalid : function(errors) {
97147         // Save the message and fire the 'invalid' event
97148         var me = this,
97149             oldMsg = me.getActiveError();
97150         me.setActiveErrors(Ext.Array.from(errors));
97151         if (oldMsg !== me.getActiveError()) {
97152             me.doComponentLayout();
97153         }
97154     },
97155
97156     /**
97157      * Clear any invalid styles/messages for this field.
97158      *
97159      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
97160      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
97161      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
97162      */
97163     clearInvalid : function() {
97164         // Clear the message and fire the 'valid' event
97165         var me = this,
97166             hadError = me.hasActiveError();
97167         me.unsetActiveError();
97168         if (hadError) {
97169             me.doComponentLayout();
97170         }
97171     },
97172
97173     /**
97174      * @private Overrides the method from the Ext.form.Labelable mixin to also add the invalidCls to the inputEl,
97175      * as that is required for proper styling in IE with nested fields (due to lack of child selector)
97176      */
97177     renderActiveError: function() {
97178         var me = this,
97179             hasError = me.hasActiveError();
97180         if (me.inputEl) {
97181             // Add/remove invalid class
97182             me.inputEl[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
97183         }
97184         me.mixins.labelable.renderActiveError.call(me);
97185     },
97186
97187
97188     getActionEl: function() {
97189         return this.inputEl || this.el;
97190     }
97191
97192 });
97193
97194 /**
97195  * @docauthor Jason Johnston <jason@sencha.com>
97196  *
97197  * A basic text field.  Can be used as a direct replacement for traditional text inputs,
97198  * or as the base class for more sophisticated input controls (like {@link Ext.form.field.TextArea}
97199  * and {@link Ext.form.field.ComboBox}). Has support for empty-field placeholder values (see {@link #emptyText}).
97200  *
97201  * # Validation
97202  *
97203  * The Text field has a useful set of validations built in:
97204  *
97205  * - {@link #allowBlank} for making the field required
97206  * - {@link #minLength} for requiring a minimum value length
97207  * - {@link #maxLength} for setting a maximum value length (with {@link #enforceMaxLength} to add it
97208  *   as the `maxlength` attribute on the input element)
97209  * - {@link #regex} to specify a custom regular expression for validation
97210  *
97211  * In addition, custom validations may be added:
97212  *
97213  * - {@link #vtype} specifies a virtual type implementation from {@link Ext.form.field.VTypes} which can contain
97214  *   custom validation logic
97215  * - {@link #validator} allows a custom arbitrary function to be called during validation
97216  *
97217  * The details around how and when each of these validation options get used are described in the
97218  * documentation for {@link #getErrors}.
97219  *
97220  * By default, the field value is checked for validity immediately while the user is typing in the
97221  * field. This can be controlled with the {@link #validateOnChange}, {@link #checkChangeEvents}, and
97222  * {@link #checkChangeBuffer} configurations. Also see the details on Form Validation in the
97223  * {@link Ext.form.Panel} class documentation.
97224  *
97225  * # Masking and Character Stripping
97226  *
97227  * Text fields can be configured with custom regular expressions to be applied to entered values before
97228  * validation: see {@link #maskRe} and {@link #stripCharsRe} for details.
97229  *
97230  * # Example usage
97231  *
97232  *     @example
97233  *     Ext.create('Ext.form.Panel', {
97234  *         title: 'Contact Info',
97235  *         width: 300,
97236  *         bodyPadding: 10,
97237  *         renderTo: Ext.getBody(),
97238  *         items: [{
97239  *             xtype: 'textfield',
97240  *             name: 'name',
97241  *             fieldLabel: 'Name',
97242  *             allowBlank: false  // requires a non-empty value
97243  *         }, {
97244  *             xtype: 'textfield',
97245  *             name: 'email',
97246  *             fieldLabel: 'Email Address',
97247  *             vtype: 'email'  // requires value to be a valid email address format
97248  *         }]
97249  *     });
97250  */
97251 Ext.define('Ext.form.field.Text', {
97252     extend:'Ext.form.field.Base',
97253     alias: 'widget.textfield',
97254     requires: ['Ext.form.field.VTypes', 'Ext.layout.component.field.Text'],
97255     alternateClassName: ['Ext.form.TextField', 'Ext.form.Text'],
97256
97257     /**
97258      * @cfg {String} vtypeText
97259      * A custom error message to display in place of the default message provided for the **`{@link #vtype}`** currently
97260      * set for this field. **Note**: only applies if **`{@link #vtype}`** is set, else ignored.
97261      */
97262
97263     /**
97264      * @cfg {RegExp} stripCharsRe
97265      * A JavaScript RegExp object used to strip unwanted content from the value
97266      * before validation. If <tt>stripCharsRe</tt> is specified,
97267      * every character matching <tt>stripCharsRe</tt> will be removed before fed to validation.
97268      * This does not change the value of the field.
97269      */
97270
97271     /**
97272      * @cfg {Number} size
97273      * An initial value for the 'size' attribute on the text input element. This is only used if the field has no
97274      * configured {@link #width} and is not given a width by its container's layout. Defaults to 20.
97275      */
97276     size: 20,
97277
97278     /**
97279      * @cfg {Boolean} [grow=false]
97280      * true if this field should automatically grow and shrink to its content
97281      */
97282
97283     /**
97284      * @cfg {Number} growMin
97285      * The minimum width to allow when `{@link #grow} = true`
97286      */
97287     growMin : 30,
97288
97289     /**
97290      * @cfg {Number} growMax
97291      * The maximum width to allow when `{@link #grow} = true`
97292      */
97293     growMax : 800,
97294
97295     /**
97296      * @cfg {String} growAppend
97297      * A string that will be appended to the field's current value for the purposes of calculating the target field
97298      * size. Only used when the {@link #grow} config is true. Defaults to a single capital "W" (the widest character in
97299      * common fonts) to leave enough space for the next typed character and avoid the field value shifting before the
97300      * width is adjusted.
97301      */
97302     growAppend: 'W',
97303
97304     /**
97305      * @cfg {String} vtype
97306      * A validation type name as defined in {@link Ext.form.field.VTypes}
97307      */
97308
97309     /**
97310      * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes (character being
97311      * typed) that do not match.
97312      * Note: It dose not filter characters already in the input.
97313      */
97314
97315     /**
97316      * @cfg {Boolean} [disableKeyFilter=false]
97317      * Specify true to disable input keystroke filtering
97318      */
97319
97320     /**
97321      * @cfg {Boolean} allowBlank
97322      * Specify false to validate that the value's length is > 0
97323      */
97324     allowBlank : true,
97325
97326     /**
97327      * @cfg {Number} minLength
97328      * Minimum input field length required
97329      */
97330     minLength : 0,
97331
97332     /**
97333      * @cfg {Number} maxLength
97334      * Maximum input field length allowed by validation (defaults to Number.MAX_VALUE). This behavior is intended to
97335      * provide instant feedback to the user by improving usability to allow pasting and editing or overtyping and back
97336      * tracking. To restrict the maximum number of characters that can be entered into the field use the **{@link
97337      * Ext.form.field.Text#enforceMaxLength enforceMaxLength}** option.
97338      */
97339     maxLength : Number.MAX_VALUE,
97340
97341     /**
97342      * @cfg {Boolean} enforceMaxLength
97343      * True to set the maxLength property on the underlying input field. Defaults to false
97344      */
97345
97346     /**
97347      * @cfg {String} minLengthText
97348      * Error text to display if the **{@link #minLength minimum length}** validation fails.
97349      */
97350     minLengthText : 'The minimum length for this field is {0}',
97351
97352     /**
97353      * @cfg {String} maxLengthText
97354      * Error text to display if the **{@link #maxLength maximum length}** validation fails
97355      */
97356     maxLengthText : 'The maximum length for this field is {0}',
97357
97358     /**
97359      * @cfg {Boolean} [selectOnFocus=false]
97360      * true to automatically select any existing field text when the field receives input focus
97361      */
97362
97363     /**
97364      * @cfg {String} blankText
97365      * The error text to display if the **{@link #allowBlank}** validation fails
97366      */
97367     blankText : 'This field is required',
97368
97369     /**
97370      * @cfg {Function} validator
97371      * A custom validation function to be called during field validation ({@link #getErrors}).
97372      * If specified, this function will be called first, allowing the developer to override the default validation
97373      * process.
97374      *
97375      * This function will be passed the following parameters:
97376      *
97377      * @cfg {Object} validator.value The current field value
97378      * @cfg {Boolean/String} validator.return
97379      *
97380      * - True if the value is valid
97381      * - An error message if the value is invalid
97382      */
97383
97384     /**
97385      * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation.
97386      * If the test fails, the field will be marked invalid using
97387      * either <b><tt>{@link #regexText}</tt></b> or <b><tt>{@link #invalidText}</tt></b>.
97388      */
97389
97390     /**
97391      * @cfg {String} regexText
97392      * The error text to display if **{@link #regex}** is used and the test fails during validation
97393      */
97394     regexText : '',
97395
97396     /**
97397      * @cfg {String} emptyText
97398      * The default text to place into an empty field.
97399      *
97400      * Note that normally this value will be submitted to the server if this field is enabled; to prevent this you can
97401      * set the {@link Ext.form.action.Action#submitEmptyText submitEmptyText} option of {@link Ext.form.Basic#submit} to
97402      * false.
97403      *
97404      * Also note that if you use {@link #inputType inputType}:'file', {@link #emptyText} is not supported and should be
97405      * avoided.
97406      */
97407
97408     /**
97409      * @cfg {String} [emptyCls='x-form-empty-field']
97410      * The CSS class to apply to an empty field to style the **{@link #emptyText}**.
97411      * This class is automatically added and removed as needed depending on the current field value.
97412      */
97413     emptyCls : Ext.baseCSSPrefix + 'form-empty-field',
97414
97415     ariaRole: 'textbox',
97416
97417     /**
97418      * @cfg {Boolean} [enableKeyEvents=false]
97419      * true to enable the proxying of key events for the HTML input field
97420      */
97421
97422     componentLayout: 'textfield',
97423
97424     initComponent : function(){
97425         this.callParent();
97426         this.addEvents(
97427             /**
97428              * @event autosize
97429              * Fires when the **{@link #autoSize}** function is triggered and the field is resized according to the
97430              * {@link #grow}/{@link #growMin}/{@link #growMax} configs as a result. This event provides a hook for the
97431              * developer to apply additional logic at runtime to resize the field if needed.
97432              * @param {Ext.form.field.Text} this This text field
97433              * @param {Number} width The new field width
97434              */
97435             'autosize',
97436
97437             /**
97438              * @event keydown
97439              * Keydown input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
97440              * @param {Ext.form.field.Text} this This text field
97441              * @param {Ext.EventObject} e
97442              */
97443             'keydown',
97444             /**
97445              * @event keyup
97446              * Keyup input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
97447              * @param {Ext.form.field.Text} this This text field
97448              * @param {Ext.EventObject} e
97449              */
97450             'keyup',
97451             /**
97452              * @event keypress
97453              * Keypress input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
97454              * @param {Ext.form.field.Text} this This text field
97455              * @param {Ext.EventObject} e
97456              */
97457             'keypress'
97458         );
97459     },
97460
97461     // private
97462     initEvents : function(){
97463         var me = this,
97464             el = me.inputEl;
97465
97466         me.callParent();
97467         if(me.selectOnFocus || me.emptyText){
97468             me.mon(el, 'mousedown', me.onMouseDown, me);
97469         }
97470         if(me.maskRe || (me.vtype && me.disableKeyFilter !== true && (me.maskRe = Ext.form.field.VTypes[me.vtype+'Mask']))){
97471             me.mon(el, 'keypress', me.filterKeys, me);
97472         }
97473
97474         if (me.enableKeyEvents) {
97475             me.mon(el, {
97476                 scope: me,
97477                 keyup: me.onKeyUp,
97478                 keydown: me.onKeyDown,
97479                 keypress: me.onKeyPress
97480             });
97481         }
97482     },
97483
97484     /**
97485      * @private
97486      * Override. Treat undefined and null values as equal to an empty string value.
97487      */
97488     isEqual: function(value1, value2) {
97489         return this.isEqualAsString(value1, value2);
97490     },
97491
97492     /**
97493      * @private
97494      * If grow=true, invoke the autoSize method when the field's value is changed.
97495      */
97496     onChange: function() {
97497         this.callParent();
97498         this.autoSize();
97499     },
97500
97501     afterRender: function(){
97502         var me = this;
97503         if (me.enforceMaxLength) {
97504             me.inputEl.dom.maxLength = me.maxLength;
97505         }
97506         me.applyEmptyText();
97507         me.autoSize();
97508         me.callParent();
97509     },
97510
97511     onMouseDown: function(e){
97512         var me = this;
97513         if(!me.hasFocus){
97514             me.mon(me.inputEl, 'mouseup', Ext.emptyFn, me, { single: true, preventDefault: true });
97515         }
97516     },
97517
97518     /**
97519      * Performs any necessary manipulation of a raw String value to prepare it for conversion and/or
97520      * {@link #validate validation}. For text fields this applies the configured {@link #stripCharsRe}
97521      * to the raw value.
97522      * @param {String} value The unprocessed string value
97523      * @return {String} The processed string value
97524      */
97525     processRawValue: function(value) {
97526         var me = this,
97527             stripRe = me.stripCharsRe,
97528             newValue;
97529
97530         if (stripRe) {
97531             newValue = value.replace(stripRe, '');
97532             if (newValue !== value) {
97533                 me.setRawValue(newValue);
97534                 value = newValue;
97535             }
97536         }
97537         return value;
97538     },
97539
97540     //private
97541     onDisable: function(){
97542         this.callParent();
97543         if (Ext.isIE) {
97544             this.inputEl.dom.unselectable = 'on';
97545         }
97546     },
97547
97548     //private
97549     onEnable: function(){
97550         this.callParent();
97551         if (Ext.isIE) {
97552             this.inputEl.dom.unselectable = '';
97553         }
97554     },
97555
97556     onKeyDown: function(e) {
97557         this.fireEvent('keydown', this, e);
97558     },
97559
97560     onKeyUp: function(e) {
97561         this.fireEvent('keyup', this, e);
97562     },
97563
97564     onKeyPress: function(e) {
97565         this.fireEvent('keypress', this, e);
97566     },
97567
97568     /**
97569      * Resets the current field value to the originally-loaded value and clears any validation messages.
97570      * Also adds **{@link #emptyText}** and **{@link #emptyCls}** if the original value was blank.
97571      */
97572     reset : function(){
97573         this.callParent();
97574         this.applyEmptyText();
97575     },
97576
97577     applyEmptyText : function(){
97578         var me = this,
97579             emptyText = me.emptyText,
97580             isEmpty;
97581
97582         if (me.rendered && emptyText) {
97583             isEmpty = me.getRawValue().length < 1 && !me.hasFocus;
97584
97585             if (Ext.supports.Placeholder) {
97586                 me.inputEl.dom.placeholder = emptyText;
97587             } else if (isEmpty) {
97588                 me.setRawValue(emptyText);
97589             }
97590
97591             //all browsers need this because of a styling issue with chrome + placeholders.
97592             //the text isnt vertically aligned when empty (and using the placeholder)
97593             if (isEmpty) {
97594                 me.inputEl.addCls(me.emptyCls);
97595             }
97596
97597             me.autoSize();
97598         }
97599     },
97600
97601     // private
97602     preFocus : function(){
97603         var me = this,
97604             inputEl = me.inputEl,
97605             emptyText = me.emptyText,
97606             isEmpty;
97607
97608         if (emptyText && !Ext.supports.Placeholder && inputEl.dom.value === emptyText) {
97609             me.setRawValue('');
97610             isEmpty = true;
97611             inputEl.removeCls(me.emptyCls);
97612         } else if (Ext.supports.Placeholder) {
97613             me.inputEl.removeCls(me.emptyCls);
97614         }
97615         if (me.selectOnFocus || isEmpty) {
97616             inputEl.dom.select();
97617         }
97618     },
97619
97620     onFocus: function() {
97621         var me = this;
97622         me.callParent(arguments);
97623         if (me.emptyText) {
97624             me.autoSize();
97625         }
97626     },
97627
97628     // private
97629     postBlur : function(){
97630         this.applyEmptyText();
97631     },
97632
97633     // private
97634     filterKeys : function(e){
97635         /*
97636          * On European keyboards, the right alt key, Alt Gr, is used to type certain special characters.
97637          * JS detects a keypress of this as ctrlKey & altKey. As such, we check that alt isn't pressed
97638          * so we can still process these special characters.
97639          */
97640         if (e.ctrlKey && !e.altKey) {
97641             return;
97642         }
97643         var key = e.getKey(),
97644             charCode = String.fromCharCode(e.getCharCode());
97645
97646         if(Ext.isGecko && (e.isNavKeyPress() || key === e.BACKSPACE || (key === e.DELETE && e.button === -1))){
97647             return;
97648         }
97649
97650         if(!Ext.isGecko && e.isSpecialKey() && !charCode){
97651             return;
97652         }
97653         if(!this.maskRe.test(charCode)){
97654             e.stopEvent();
97655         }
97656     },
97657
97658     /**
97659      * Returns the raw String value of the field, without performing any normalization, conversion, or validation. Gets
97660      * the current value of the input element if the field has been rendered, ignoring the value if it is the
97661      * {@link #emptyText}. To get a normalized and converted value see {@link #getValue}.
97662      * @return {String} The raw String value of the field
97663      */
97664     getRawValue: function() {
97665         var me = this,
97666             v = me.callParent();
97667         if (v === me.emptyText) {
97668             v = '';
97669         }
97670         return v;
97671     },
97672
97673     /**
97674      * Sets a data value into the field and runs the change detection and validation. Also applies any configured
97675      * {@link #emptyText} for text fields. To set the value directly without these inspections see {@link #setRawValue}.
97676      * @param {Object} value The value to set
97677      * @return {Ext.form.field.Text} this
97678      */
97679     setValue: function(value) {
97680         var me = this,
97681             inputEl = me.inputEl;
97682
97683         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
97684             inputEl.removeCls(me.emptyCls);
97685         }
97686
97687         me.callParent(arguments);
97688
97689         me.applyEmptyText();
97690         return me;
97691     },
97692
97693     /**
97694      * Validates a value according to the field's validation rules and returns an array of errors
97695      * for any failing validations. Validation rules are processed in the following order:
97696      *
97697      * 1. **Field specific validator**
97698      *
97699      *     A validator offers a way to customize and reuse a validation specification.
97700      *     If a field is configured with a `{@link #validator}`
97701      *     function, it will be passed the current field value.  The `{@link #validator}`
97702      *     function is expected to return either:
97703      *
97704      *     - Boolean `true`  if the value is valid (validation continues).
97705      *     - a String to represent the invalid message if invalid (validation halts).
97706      *
97707      * 2. **Basic Validation**
97708      *
97709      *     If the `{@link #validator}` has not halted validation,
97710      *     basic validation proceeds as follows:
97711      *
97712      *     - `{@link #allowBlank}` : (Invalid message = `{@link #emptyText}`)
97713      *
97714      *         Depending on the configuration of `{@link #allowBlank}`, a
97715      *         blank field will cause validation to halt at this step and return
97716      *         Boolean true or false accordingly.
97717      *
97718      *     - `{@link #minLength}` : (Invalid message = `{@link #minLengthText}`)
97719      *
97720      *         If the passed value does not satisfy the `{@link #minLength}`
97721      *         specified, validation halts.
97722      *
97723      *     -  `{@link #maxLength}` : (Invalid message = `{@link #maxLengthText}`)
97724      *
97725      *         If the passed value does not satisfy the `{@link #maxLength}`
97726      *         specified, validation halts.
97727      *
97728      * 3. **Preconfigured Validation Types (VTypes)**
97729      *
97730      *     If none of the prior validation steps halts validation, a field
97731      *     configured with a `{@link #vtype}` will utilize the
97732      *     corresponding {@link Ext.form.field.VTypes VTypes} validation function.
97733      *     If invalid, either the field's `{@link #vtypeText}` or
97734      *     the VTypes vtype Text property will be used for the invalid message.
97735      *     Keystrokes on the field will be filtered according to the VTypes
97736      *     vtype Mask property.
97737      *
97738      * 4. **Field specific regex test**
97739      *
97740      *     If none of the prior validation steps halts validation, a field's
97741      *     configured <code>{@link #regex}</code> test will be processed.
97742      *     The invalid message for this test is configured with `{@link #regexText}`
97743      *
97744      * @param {Object} value The value to validate. The processed raw value will be used if nothing is passed.
97745      * @return {String[]} Array of any validation errors
97746      */
97747     getErrors: function(value) {
97748         var me = this,
97749             errors = me.callParent(arguments),
97750             validator = me.validator,
97751             emptyText = me.emptyText,
97752             allowBlank = me.allowBlank,
97753             vtype = me.vtype,
97754             vtypes = Ext.form.field.VTypes,
97755             regex = me.regex,
97756             format = Ext.String.format,
97757             msg;
97758
97759         value = value || me.processRawValue(me.getRawValue());
97760
97761         if (Ext.isFunction(validator)) {
97762             msg = validator.call(me, value);
97763             if (msg !== true) {
97764                 errors.push(msg);
97765             }
97766         }
97767
97768         if (value.length < 1 || value === emptyText) {
97769             if (!allowBlank) {
97770                 errors.push(me.blankText);
97771             }
97772             //if value is blank, there cannot be any additional errors
97773             return errors;
97774         }
97775
97776         if (value.length < me.minLength) {
97777             errors.push(format(me.minLengthText, me.minLength));
97778         }
97779
97780         if (value.length > me.maxLength) {
97781             errors.push(format(me.maxLengthText, me.maxLength));
97782         }
97783
97784         if (vtype) {
97785             if(!vtypes[vtype](value, me)){
97786                 errors.push(me.vtypeText || vtypes[vtype +'Text']);
97787             }
97788         }
97789
97790         if (regex && !regex.test(value)) {
97791             errors.push(me.regexText || me.invalidText);
97792         }
97793
97794         return errors;
97795     },
97796
97797     /**
97798      * Selects text in this field
97799      * @param {Number} [start=0] The index where the selection should start
97800      * @param {Number} [end] The index where the selection should end (defaults to the text length)
97801      */
97802     selectText : function(start, end){
97803         var me = this,
97804             v = me.getRawValue(),
97805             doFocus = true,
97806             el = me.inputEl.dom,
97807             undef,
97808             range;
97809
97810         if (v.length > 0) {
97811             start = start === undef ? 0 : start;
97812             end = end === undef ? v.length : end;
97813             if (el.setSelectionRange) {
97814                 el.setSelectionRange(start, end);
97815             }
97816             else if(el.createTextRange) {
97817                 range = el.createTextRange();
97818                 range.moveStart('character', start);
97819                 range.moveEnd('character', end - v.length);
97820                 range.select();
97821             }
97822             doFocus = Ext.isGecko || Ext.isOpera;
97823         }
97824         if (doFocus) {
97825             me.focus();
97826         }
97827     },
97828
97829     /**
97830      * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed. This
97831      * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the width changes.
97832      */
97833     autoSize: function() {
97834         var me = this,
97835             width;
97836         if (me.grow && me.rendered) {
97837             me.doComponentLayout();
97838             width = me.inputEl.getWidth();
97839             if (width !== me.lastInputWidth) {
97840                 me.fireEvent('autosize', width);
97841                 me.lastInputWidth = width;
97842             }
97843         }
97844     },
97845
97846     initAria: function() {
97847         this.callParent();
97848         this.getActionEl().dom.setAttribute('aria-required', this.allowBlank === false);
97849     },
97850
97851     /**
97852      * To get the natural width of the inputEl, we do a simple calculation based on the 'size' config. We use
97853      * hard-coded numbers to approximate what browsers do natively, to avoid having to read any styles which would hurt
97854      * performance. Overrides Labelable method.
97855      * @protected
97856      */
97857     getBodyNaturalWidth: function() {
97858         return Math.round(this.size * 6.5) + 20;
97859     }
97860
97861 });
97862
97863 /**
97864  * @docauthor Robert Dougan <rob@sencha.com>
97865  *
97866  * This class creates a multiline text field, which can be used as a direct replacement for traditional
97867  * textarea fields. In addition, it supports automatically {@link #grow growing} the height of the textarea to
97868  * fit its content.
97869  *
97870  * All of the configuration options from {@link Ext.form.field.Text} can be used on TextArea.
97871  *
97872  * Example usage:
97873  *
97874  *     @example
97875  *     Ext.create('Ext.form.FormPanel', {
97876  *         title      : 'Sample TextArea',
97877  *         width      : 400,
97878  *         bodyPadding: 10,
97879  *         renderTo   : Ext.getBody(),
97880  *         items: [{
97881  *             xtype     : 'textareafield',
97882  *             grow      : true,
97883  *             name      : 'message',
97884  *             fieldLabel: 'Message',
97885  *             anchor    : '100%'
97886  *         }]
97887  *     });
97888  *
97889  * Some other useful configuration options when using {@link #grow} are {@link #growMin} and {@link #growMax}.
97890  * These allow you to set the minimum and maximum grow heights for the textarea.
97891  */
97892 Ext.define('Ext.form.field.TextArea', {
97893     extend:'Ext.form.field.Text',
97894     alias: ['widget.textareafield', 'widget.textarea'],
97895     alternateClassName: 'Ext.form.TextArea',
97896     requires: ['Ext.XTemplate', 'Ext.layout.component.field.TextArea'],
97897
97898     fieldSubTpl: [
97899         '<textarea id="{id}" ',
97900             '<tpl if="name">name="{name}" </tpl>',
97901             '<tpl if="rows">rows="{rows}" </tpl>',
97902             '<tpl if="cols">cols="{cols}" </tpl>',
97903             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
97904             'class="{fieldCls} {typeCls}" ',
97905             'autocomplete="off">',
97906         '</textarea>',
97907         {
97908             compiled: true,
97909             disableFormats: true
97910         }
97911     ],
97912
97913     /**
97914      * @cfg {Number} growMin
97915      * The minimum height to allow when {@link #grow}=true
97916      */
97917     growMin: 60,
97918
97919     /**
97920      * @cfg {Number} growMax
97921      * The maximum height to allow when {@link #grow}=true
97922      */
97923     growMax: 1000,
97924
97925     /**
97926      * @cfg {String} growAppend
97927      * A string that will be appended to the field's current value for the purposes of calculating the target field
97928      * size. Only used when the {@link #grow} config is true. Defaults to a newline for TextArea to ensure there is
97929      * always a space below the current line.
97930      */
97931     growAppend: '\n-',
97932
97933     /**
97934      * @cfg {Number} cols
97935      * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
97936      * configured {@link #width} and is not given a width by its container's layout.
97937      */
97938     cols: 20,
97939
97940     /**
97941      * @cfg {Number} cols
97942      * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
97943      * configured {@link #width} and is not given a width by its container's layout.
97944      */
97945     rows: 4,
97946
97947     /**
97948      * @cfg {Boolean} enterIsSpecial
97949      * True if you want the enter key to be classed as a special key. Special keys are generally navigation keys
97950      * (arrows, space, enter). Setting the config property to true would mean that you could not insert returns into the
97951      * textarea.
97952      */
97953     enterIsSpecial: false,
97954
97955     /**
97956      * @cfg {Boolean} preventScrollbars
97957      * true to prevent scrollbars from appearing regardless of how much text is in the field. This option is only
97958      * relevant when {@link #grow} is true. Equivalent to setting overflow: hidden.
97959      */
97960     preventScrollbars: false,
97961
97962     // private
97963     componentLayout: 'textareafield',
97964
97965     // private
97966     onRender: function(ct, position) {
97967         var me = this;
97968         Ext.applyIf(me.subTplData, {
97969             cols: me.cols,
97970             rows: me.rows
97971         });
97972
97973         me.callParent(arguments);
97974     },
97975
97976     // private
97977     afterRender: function(){
97978         var me = this;
97979
97980         me.callParent(arguments);
97981
97982         if (me.grow) {
97983             if (me.preventScrollbars) {
97984                 me.inputEl.setStyle('overflow', 'hidden');
97985             }
97986             me.inputEl.setHeight(me.growMin);
97987         }
97988     },
97989
97990     // private
97991     fireKey: function(e) {
97992         if (e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() !== e.ENTER || e.hasModifier()))) {
97993             this.fireEvent('specialkey', this, e);
97994         }
97995     },
97996
97997     /**
97998      * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed. This
97999      * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the height changes.
98000      */
98001     autoSize: function() {
98002         var me = this,
98003             height;
98004
98005         if (me.grow && me.rendered) {
98006             me.doComponentLayout();
98007             height = me.inputEl.getHeight();
98008             if (height !== me.lastInputHeight) {
98009                 me.fireEvent('autosize', height);
98010                 me.lastInputHeight = height;
98011             }
98012         }
98013     },
98014
98015     // private
98016     initAria: function() {
98017         this.callParent(arguments);
98018         this.getActionEl().dom.setAttribute('aria-multiline', true);
98019     },
98020
98021     /**
98022      * To get the natural width of the textarea element, we do a simple calculation based on the 'cols' config.
98023      * We use hard-coded numbers to approximate what browsers do natively, to avoid having to read any styles which
98024      * would hurt performance. Overrides Labelable method.
98025      * @protected
98026      */
98027     getBodyNaturalWidth: function() {
98028         return Math.round(this.cols * 6.5) + 20;
98029     }
98030
98031 });
98032
98033
98034 /**
98035  * Utility class for generating different styles of message boxes.  The singleton instance, Ext.MessageBox
98036  * alias `Ext.Msg` can also be used.
98037  *
98038  * Note that a MessageBox is asynchronous.  Unlike a regular JavaScript `alert` (which will halt
98039  * browser execution), showing a MessageBox will not cause the code to stop.  For this reason, if you have code
98040  * that should only run *after* some user feedback from the MessageBox, you must use a callback function
98041  * (see the `function` parameter for {@link #show} for more details).
98042  *
98043  * Basic alert
98044  *
98045  *     @example
98046  *     Ext.Msg.alert('Status', 'Changes saved successfully.');
98047  *
98048  * Prompt for user data and process the result using a callback
98049  *
98050  *     @example
98051  *     Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
98052  *         if (btn == 'ok'){
98053  *             // process text value and close...
98054  *         }
98055  *     });
98056  *
98057  * Show a dialog using config options
98058  *
98059  *     @example
98060  *     Ext.Msg.show({
98061  *          title:'Save Changes?',
98062  *          msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
98063  *          buttons: Ext.Msg.YESNOCANCEL,
98064  *          icon: Ext.Msg.QUESTION
98065  *     });
98066  */
98067 Ext.define('Ext.window.MessageBox', {
98068     extend: 'Ext.window.Window',
98069
98070     requires: [
98071         'Ext.toolbar.Toolbar',
98072         'Ext.form.field.Text',
98073         'Ext.form.field.TextArea',
98074         'Ext.button.Button',
98075         'Ext.layout.container.Anchor',
98076         'Ext.layout.container.HBox',
98077         'Ext.ProgressBar'
98078     ],
98079
98080     alias: 'widget.messagebox',
98081
98082     /**
98083      * Button config that displays a single OK button
98084      * @type Number
98085      */
98086     OK : 1,
98087     /**
98088      * Button config that displays a single Yes button
98089      * @type Number
98090      */
98091     YES : 2,
98092     /**
98093      * Button config that displays a single No button
98094      * @type Number
98095      */
98096     NO : 4,
98097     /**
98098      * Button config that displays a single Cancel button
98099      * @type Number
98100      */
98101     CANCEL : 8,
98102     /**
98103      * Button config that displays OK and Cancel buttons
98104      * @type Number
98105      */
98106     OKCANCEL : 9,
98107     /**
98108      * Button config that displays Yes and No buttons
98109      * @type Number
98110      */
98111     YESNO : 6,
98112     /**
98113      * Button config that displays Yes, No and Cancel buttons
98114      * @type Number
98115      */
98116     YESNOCANCEL : 14,
98117     /**
98118      * The CSS class that provides the INFO icon image
98119      * @type String
98120      */
98121     INFO : 'ext-mb-info',
98122     /**
98123      * The CSS class that provides the WARNING icon image
98124      * @type String
98125      */
98126     WARNING : 'ext-mb-warning',
98127     /**
98128      * The CSS class that provides the QUESTION icon image
98129      * @type String
98130      */
98131     QUESTION : 'ext-mb-question',
98132     /**
98133      * The CSS class that provides the ERROR icon image
98134      * @type String
98135      */
98136     ERROR : 'ext-mb-error',
98137
98138     // hide it by offsets. Windows are hidden on render by default.
98139     hideMode: 'offsets',
98140     closeAction: 'hide',
98141     resizable: false,
98142     title: '&#160;',
98143
98144     width: 600,
98145     height: 500,
98146     minWidth: 250,
98147     maxWidth: 600,
98148     minHeight: 110,
98149     maxHeight: 500,
98150     constrain: true,
98151
98152     cls: Ext.baseCSSPrefix + 'message-box',
98153
98154     layout: {
98155         type: 'anchor'
98156     },
98157
98158     /**
98159      * The default height in pixels of the message box's multiline textarea if displayed.
98160      * @type Number
98161      */
98162     defaultTextHeight : 75,
98163     /**
98164      * The minimum width in pixels of the message box if it is a progress-style dialog.  This is useful
98165      * for setting a different minimum width than text-only dialogs may need.
98166      * @type Number
98167      */
98168     minProgressWidth : 250,
98169     /**
98170      * The minimum width in pixels of the message box if it is a prompt dialog.  This is useful
98171      * for setting a different minimum width than text-only dialogs may need.
98172      * @type Number
98173      */
98174     minPromptWidth: 250,
98175     /**
98176      * An object containing the default button text strings that can be overriden for localized language support.
98177      * Supported properties are: ok, cancel, yes and no.  Generally you should include a locale-specific
98178      * resource file for handling language support across the framework.
98179      * Customize the default text like so: Ext.window.MessageBox.buttonText.yes = "oui"; //french
98180      * @type Object
98181      */
98182     buttonText: {
98183         ok: 'OK',
98184         yes: 'Yes',
98185         no: 'No',
98186         cancel: 'Cancel'
98187     },
98188
98189     buttonIds: [
98190         'ok', 'yes', 'no', 'cancel'
98191     ],
98192
98193     titleText: {
98194         confirm: 'Confirm',
98195         prompt: 'Prompt',
98196         wait: 'Loading...',
98197         alert: 'Attention'
98198     },
98199
98200     iconHeight: 35,
98201
98202     makeButton: function(btnIdx) {
98203         var btnId = this.buttonIds[btnIdx];
98204         return Ext.create('Ext.button.Button', {
98205             handler: this.btnCallback,
98206             itemId: btnId,
98207             scope: this,
98208             text: this.buttonText[btnId],
98209             minWidth: 75
98210         });
98211     },
98212
98213     btnCallback: function(btn) {
98214         var me = this,
98215             value,
98216             field;
98217
98218         if (me.cfg.prompt || me.cfg.multiline) {
98219             if (me.cfg.multiline) {
98220                 field = me.textArea;
98221             } else {
98222                 field = me.textField;
98223             }
98224             value = field.getValue();
98225             field.reset();
98226         }
98227
98228         // Important not to have focus remain in the hidden Window; Interferes with DnD.
98229         btn.blur();
98230         me.hide();
98231         me.userCallback(btn.itemId, value, me.cfg);
98232     },
98233
98234     hide: function() {
98235         var me = this;
98236         me.dd.endDrag();
98237         me.progressBar.reset();
98238         me.removeCls(me.cfg.cls);
98239         me.callParent();
98240     },
98241
98242     initComponent: function() {
98243         var me = this,
98244             i, button;
98245
98246         me.title = '&#160;';
98247
98248         me.topContainer = Ext.create('Ext.container.Container', {
98249             anchor: '100%',
98250             style: {
98251                 padding: '10px',
98252                 overflow: 'hidden'
98253             },
98254             items: [
98255                 me.iconComponent = Ext.create('Ext.Component', {
98256                     cls: 'ext-mb-icon',
98257                     width: 50,
98258                     height: me.iconHeight,
98259                     style: {
98260                         'float': 'left'
98261                     }
98262                 }),
98263                 me.promptContainer = Ext.create('Ext.container.Container', {
98264                     layout: {
98265                         type: 'anchor'
98266                     },
98267                     items: [
98268                         me.msg = Ext.create('Ext.Component', {
98269                             autoEl: { tag: 'span' },
98270                             cls: 'ext-mb-text'
98271                         }),
98272                         me.textField = Ext.create('Ext.form.field.Text', {
98273                             anchor: '100%',
98274                             enableKeyEvents: true,
98275                             listeners: {
98276                                 keydown: me.onPromptKey,
98277                                 scope: me
98278                             }
98279                         }),
98280                         me.textArea = Ext.create('Ext.form.field.TextArea', {
98281                             anchor: '100%',
98282                             height: 75
98283                         })
98284                     ]
98285                 })
98286             ]
98287         });
98288         me.progressBar = Ext.create('Ext.ProgressBar', {
98289             anchor: '-10',
98290             style: 'margin-left:10px'
98291         });
98292
98293         me.items = [me.topContainer, me.progressBar];
98294
98295         // Create the buttons based upon passed bitwise config
98296         me.msgButtons = [];
98297         for (i = 0; i < 4; i++) {
98298             button = me.makeButton(i);
98299             me.msgButtons[button.itemId] = button;
98300             me.msgButtons.push(button);
98301         }
98302         me.bottomTb = Ext.create('Ext.toolbar.Toolbar', {
98303             ui: 'footer',
98304             dock: 'bottom',
98305             layout: {
98306                 pack: 'center'
98307             },
98308             items: [
98309                 me.msgButtons[0],
98310                 me.msgButtons[1],
98311                 me.msgButtons[2],
98312                 me.msgButtons[3]
98313             ]
98314         });
98315         me.dockedItems = [me.bottomTb];
98316
98317         me.callParent();
98318     },
98319
98320     onPromptKey: function(textField, e) {
98321         var me = this,
98322             blur;
98323
98324         if (e.keyCode === Ext.EventObject.RETURN || e.keyCode === 10) {
98325             if (me.msgButtons.ok.isVisible()) {
98326                 blur = true;
98327                 me.msgButtons.ok.handler.call(me, me.msgButtons.ok);
98328             } else if (me.msgButtons.yes.isVisible()) {
98329                 me.msgButtons.yes.handler.call(me, me.msgButtons.yes);
98330                 blur = true;
98331             }
98332
98333             if (blur) {
98334                 me.textField.blur();
98335             }
98336         }
98337     },
98338
98339     reconfigure: function(cfg) {
98340         var me = this,
98341             buttons = cfg.buttons || 0,
98342             hideToolbar = true,
98343             initialWidth = me.maxWidth,
98344             i;
98345
98346         cfg = cfg || {};
98347         me.cfg = cfg;
98348         if (cfg.width) {
98349             initialWidth = cfg.width;
98350         }
98351
98352         // Default to allowing the Window to take focus.
98353         delete me.defaultFocus;
98354
98355         // clear any old animateTarget
98356         me.animateTarget = cfg.animateTarget || undefined;
98357
98358         // Defaults to modal
98359         me.modal = cfg.modal !== false;
98360
98361         // Show the title
98362         if (cfg.title) {
98363             me.setTitle(cfg.title||'&#160;');
98364         }
98365
98366         if (!me.rendered) {
98367             me.width = initialWidth;
98368             me.render(Ext.getBody());
98369         } else {
98370             me.setSize(initialWidth, me.maxHeight);
98371         }
98372         me.setPosition(-10000, -10000);
98373
98374         // Hide or show the close tool
98375         me.closable = cfg.closable && !cfg.wait;
98376         me.header.child('[type=close]').setVisible(cfg.closable !== false);
98377
98378         // Hide or show the header
98379         if (!cfg.title && !me.closable) {
98380             me.header.hide();
98381         } else {
98382             me.header.show();
98383         }
98384
98385         // Default to dynamic drag: drag the window, not a ghost
98386         me.liveDrag = !cfg.proxyDrag;
98387
98388         // wrap the user callback
98389         me.userCallback = Ext.Function.bind(cfg.callback ||cfg.fn || Ext.emptyFn, cfg.scope || Ext.global);
98390
98391         // Hide or show the icon Component
98392         me.setIcon(cfg.icon);
98393
98394         // Hide or show the message area
98395         if (cfg.msg) {
98396             me.msg.update(cfg.msg);
98397             me.msg.show();
98398         } else {
98399             me.msg.hide();
98400         }
98401
98402         // Hide or show the input field
98403         if (cfg.prompt || cfg.multiline) {
98404             me.multiline = cfg.multiline;
98405             if (cfg.multiline) {
98406                 me.textArea.setValue(cfg.value);
98407                 me.textArea.setHeight(cfg.defaultTextHeight || me.defaultTextHeight);
98408                 me.textArea.show();
98409                 me.textField.hide();
98410                 me.defaultFocus = me.textArea;
98411             } else {
98412                 me.textField.setValue(cfg.value);
98413                 me.textArea.hide();
98414                 me.textField.show();
98415                 me.defaultFocus = me.textField;
98416             }
98417         } else {
98418             me.textArea.hide();
98419             me.textField.hide();
98420         }
98421
98422         // Hide or show the progress bar
98423         if (cfg.progress || cfg.wait) {
98424             me.progressBar.show();
98425             me.updateProgress(0, cfg.progressText);
98426             if(cfg.wait === true){
98427                 me.progressBar.wait(cfg.waitConfig);
98428             }
98429         } else {
98430             me.progressBar.hide();
98431         }
98432
98433         // Hide or show buttons depending on flag value sent.
98434         for (i = 0; i < 4; i++) {
98435             if (buttons & Math.pow(2, i)) {
98436
98437                 // Default to focus on the first visible button if focus not already set
98438                 if (!me.defaultFocus) {
98439                     me.defaultFocus = me.msgButtons[i];
98440                 }
98441                 me.msgButtons[i].show();
98442                 hideToolbar = false;
98443             } else {
98444                 me.msgButtons[i].hide();
98445             }
98446         }
98447
98448         // Hide toolbar if no buttons to show
98449         if (hideToolbar) {
98450             me.bottomTb.hide();
98451         } else {
98452             me.bottomTb.show();
98453         }
98454     },
98455
98456     /**
98457      * Displays a new message box, or reinitializes an existing message box, based on the config options
98458      * passed in. All display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally,
98459      * although those calls are basic shortcuts and do not support all of the config options allowed here.
98460      * @param {Object} config The following config options are supported: <ul>
98461      * <li><b>animateTarget</b> : String/Element<div class="sub-desc">An id or Element from which the message box should animate as it
98462      * opens and closes (defaults to undefined)</div></li>
98463      * <li><b>buttons</b> : Number<div class="sub-desc">A bitwise button specifier consisting of the sum of any of the following constants:<ul>
98464      * <li>Ext.window.MessageBox.OK</li>
98465      * <li>Ext.window.MessageBox.YES</li>
98466      * <li>Ext.window.MessageBox.NO</li>
98467      * <li>Ext.window.MessageBox.CANCEL</li>
98468      * </ul>Or false to not show any buttons (defaults to false)</div></li>
98469      * <li><b>closable</b> : Boolean<div class="sub-desc">False to hide the top-right close button (defaults to true). Note that
98470      * progress and wait dialogs will ignore this property and always hide the close button as they can only
98471      * be closed programmatically.</div></li>
98472      * <li><b>cls</b> : String<div class="sub-desc">A custom CSS class to apply to the message box's container element</div></li>
98473      * <li><b>defaultTextHeight</b> : Number<div class="sub-desc">The default height in pixels of the message box's multiline textarea
98474      * if displayed (defaults to 75)</div></li>
98475      * <li><b>fn</b> : Function<div class="sub-desc">A callback function which is called when the dialog is dismissed either
98476      * by clicking on the configured buttons, or on the dialog close button, or by pressing
98477      * the return button to enter input.
98478      * <p>Progress and wait dialogs will ignore this option since they do not respond to user
98479      * actions and can only be closed programmatically, so any required function should be called
98480      * by the same code after it closes the dialog. Parameters passed:<ul>
98481      * <li><b>buttonId</b> : String<div class="sub-desc">The ID of the button pressed, one of:<div class="sub-desc"><ul>
98482      * <li><tt>ok</tt></li>
98483      * <li><tt>yes</tt></li>
98484      * <li><tt>no</tt></li>
98485      * <li><tt>cancel</tt></li>
98486      * </ul></div></div></li>
98487      * <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>
98488      * or <tt><a href="#show-option-multiline" ext:member="show-option-multiline" ext:cls="Ext.window.MessageBox">multiline</a></tt> is true</div></li>
98489      * <li><b>opt</b> : Object<div class="sub-desc">The config object passed to show.</div></li>
98490      * </ul></p></div></li>
98491      * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code>this</code> reference) in which the function will be executed.</div></li>
98492      * <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
98493      * dialog (e.g. Ext.window.MessageBox.WARNING or 'custom-class') (defaults to '')</div></li>
98494      * <li><b>iconCls</b> : String<div class="sub-desc">The standard {@link Ext.window.Window#iconCls} to
98495      * add an optional header icon (defaults to '')</div></li>
98496      * <li><b>maxWidth</b> : Number<div class="sub-desc">The maximum width in pixels of the message box (defaults to 600)</div></li>
98497      * <li><b>minWidth</b> : Number<div class="sub-desc">The minimum width in pixels of the message box (defaults to 100)</div></li>
98498      * <li><b>modal</b> : Boolean<div class="sub-desc">False to allow user interaction with the page while the message box is
98499      * displayed (defaults to true)</div></li>
98500      * <li><b>msg</b> : String<div class="sub-desc">A string that will replace the existing message box body text (defaults to the
98501      * XHTML-compliant non-breaking space character '&amp;#160;')</div></li>
98502      * <li><a id="show-option-multiline"></a><b>multiline</b> : Boolean<div class="sub-desc">
98503      * True to prompt the user to enter multi-line text (defaults to false)</div></li>
98504      * <li><b>progress</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
98505      * <li><b>progressText</b> : String<div class="sub-desc">The text to display inside the progress bar if progress = true (defaults to '')</div></li>
98506      * <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>
98507      * <li><b>proxyDrag</b> : Boolean<div class="sub-desc">True to display a lightweight proxy while dragging (defaults to false)</div></li>
98508      * <li><b>title</b> : String<div class="sub-desc">The title text</div></li>
98509      * <li><b>value</b> : String<div class="sub-desc">The string value to set into the active textbox element if displayed</div></li>
98510      * <li><b>wait</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
98511      * <li><b>waitConfig</b> : Object<div class="sub-desc">A {@link Ext.ProgressBar#wait} config object (applies only if wait = true)</div></li>
98512      * <li><b>width</b> : Number<div class="sub-desc">The width of the dialog in pixels</div></li>
98513      * </ul>
98514      * Example usage:
98515      * <pre><code>
98516 Ext.Msg.show({
98517 title: 'Address',
98518 msg: 'Please enter your address:',
98519 width: 300,
98520 buttons: Ext.Msg.OKCANCEL,
98521 multiline: true,
98522 fn: saveAddress,
98523 animateTarget: 'addAddressBtn',
98524 icon: Ext.window.MessageBox.INFO
98525 });
98526 </code></pre>
98527      * @return {Ext.window.MessageBox} this
98528      */
98529     show: function(cfg) {
98530         var me = this;
98531
98532         me.reconfigure(cfg);
98533         me.addCls(cfg.cls);
98534         if (cfg.animateTarget) {
98535             me.doAutoSize(true);
98536             me.callParent();
98537         } else {
98538             me.callParent();
98539             me.doAutoSize(true);
98540         }
98541         return me;
98542     },
98543
98544     afterShow: function(){
98545         if (this.animateTarget) {
98546             this.center();
98547         }
98548         this.callParent(arguments);
98549     },
98550
98551     doAutoSize: function(center) {
98552         var me = this,
98553             icon = me.iconComponent,
98554             iconHeight = me.iconHeight;
98555
98556         if (!Ext.isDefined(me.frameWidth)) {
98557             me.frameWidth = me.el.getWidth() - me.body.getWidth();
98558         }
98559
98560         // reset to the original dimensions
98561         icon.setHeight(iconHeight);
98562
98563         // Allow per-invocation override of minWidth
98564         me.minWidth = me.cfg.minWidth || Ext.getClass(this).prototype.minWidth;
98565
98566         // Set best possible size based upon allowing the text to wrap in the maximized Window, and
98567         // then constraining it to within the max with. Then adding up constituent element heights.
98568         me.topContainer.doLayout();
98569         if (Ext.isIE6 || Ext.isIEQuirks) {
98570             // In IE quirks, the initial full width of the prompt fields will prevent the container element
98571             // from collapsing once sized down, so temporarily force them to a small width. They'll get
98572             // layed out to their final width later when setting the final window size.
98573             me.textField.setCalculatedSize(9);
98574             me.textArea.setCalculatedSize(9);
98575         }
98576         var width = me.cfg.width || me.msg.getWidth() + icon.getWidth() + 25, /* topContainer's layout padding */
98577             height = (me.header.rendered ? me.header.getHeight() : 0) +
98578             Math.max(me.promptContainer.getHeight(), icon.getHeight()) +
98579             me.progressBar.getHeight() +
98580             (me.bottomTb.rendered ? me.bottomTb.getHeight() : 0) + 20 ;/* topContainer's layout padding */
98581
98582         // Update to the size of the content, this way the text won't wrap under the icon.
98583         icon.setHeight(Math.max(iconHeight, me.msg.getHeight()));
98584         me.setSize(width + me.frameWidth, height + me.frameWidth);
98585         if (center) {
98586             me.center();
98587         }
98588         return me;
98589     },
98590
98591     updateText: function(text) {
98592         this.msg.update(text);
98593         return this.doAutoSize(true);
98594     },
98595
98596     /**
98597      * Adds the specified icon to the dialog.  By default, the class 'ext-mb-icon' is applied for default
98598      * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('')
98599      * to clear any existing icon. This method must be called before the MessageBox is shown.
98600      * The following built-in icon classes are supported, but you can also pass in a custom class name:
98601      * <pre>
98602 Ext.window.MessageBox.INFO
98603 Ext.window.MessageBox.WARNING
98604 Ext.window.MessageBox.QUESTION
98605 Ext.window.MessageBox.ERROR
98606      *</pre>
98607      * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon
98608      * @return {Ext.window.MessageBox} this
98609      */
98610     setIcon : function(icon) {
98611         var me = this;
98612         me.iconComponent.removeCls(me.iconCls);
98613         if (icon) {
98614             me.iconComponent.show();
98615             me.iconComponent.addCls(Ext.baseCSSPrefix + 'dlg-icon');
98616             me.iconComponent.addCls(me.iconCls = icon);
98617         } else {
98618             me.iconComponent.removeCls(Ext.baseCSSPrefix + 'dlg-icon');
98619             me.iconComponent.hide();
98620         }
98621         return me;
98622     },
98623
98624     /**
98625      * Updates a progress-style message box's text and progress bar. Only relevant on message boxes
98626      * initiated via {@link Ext.window.MessageBox#progress} or {@link Ext.window.MessageBox#wait},
98627      * or by calling {@link Ext.window.MessageBox#show} with progress: true.
98628      * @param {Number} [value=0] Any number between 0 and 1 (e.g., .5)
98629      * @param {String} [progressText=''] The progress text to display inside the progress bar.
98630      * @param {String} [msg] The message box's body text is replaced with the specified string (defaults to undefined
98631      * so that any existing body text will not get overwritten by default unless a new value is passed in)
98632      * @return {Ext.window.MessageBox} this
98633      */
98634     updateProgress : function(value, progressText, msg){
98635         this.progressBar.updateProgress(value, progressText);
98636         if (msg){
98637             this.updateText(msg);
98638         }
98639         return this;
98640     },
98641
98642     onEsc: function() {
98643         if (this.closable !== false) {
98644             this.callParent(arguments);
98645         }
98646     },
98647
98648     /**
98649      * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm).
98650      * If a callback function is passed it will be called after the user clicks either button,
98651      * and the id of the button that was clicked will be passed as the only parameter to the callback
98652      * (could also be the top-right close button).
98653      * @param {String} title The title bar text
98654      * @param {String} msg The message box body text
98655      * @param {Function} fn (optional) The callback function invoked after the message box is closed
98656      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
98657      * @return {Ext.window.MessageBox} this
98658      */
98659     confirm: function(cfg, msg, fn, scope) {
98660         if (Ext.isString(cfg)) {
98661             cfg = {
98662                 title: cfg,
98663                 icon: 'ext-mb-question',
98664                 msg: msg,
98665                 buttons: this.YESNO,
98666                 callback: fn,
98667                 scope: scope
98668             };
98669         }
98670         return this.show(cfg);
98671     },
98672
98673     /**
98674      * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt).
98675      * The prompt can be a single-line or multi-line textbox.  If a callback function is passed it will be called after the user
98676      * clicks either button, and the id of the button that was clicked (could also be the top-right
98677      * close button) and the text that was entered will be passed as the two parameters to the callback.
98678      * @param {String} title The title bar text
98679      * @param {String} msg The message box body text
98680      * @param {Function} [fn] The callback function invoked after the message box is closed
98681      * @param {Object} [scope] The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
98682      * @param {Boolean/Number} [multiline=false] True to create a multiline textbox using the defaultTextHeight
98683      * property, or the height in pixels to create the textbox/
98684      * @param {String} [value=''] Default value of the text input element
98685      * @return {Ext.window.MessageBox} this
98686      */
98687     prompt : function(cfg, msg, fn, scope, multiline, value){
98688         if (Ext.isString(cfg)) {
98689             cfg = {
98690                 prompt: true,
98691                 title: cfg,
98692                 minWidth: this.minPromptWidth,
98693                 msg: msg,
98694                 buttons: this.OKCANCEL,
98695                 callback: fn,
98696                 scope: scope,
98697                 multiline: multiline,
98698                 value: value
98699             };
98700         }
98701         return this.show(cfg);
98702     },
98703
98704     /**
98705      * Displays a message box with an infinitely auto-updating progress bar.  This can be used to block user
98706      * interaction while waiting for a long-running process to complete that does not have defined intervals.
98707      * You are responsible for closing the message box when the process is complete.
98708      * @param {String} msg The message box body text
98709      * @param {String} title (optional) The title bar text
98710      * @param {Object} config (optional) A {@link Ext.ProgressBar#wait} config object
98711      * @return {Ext.window.MessageBox} this
98712      */
98713     wait : function(cfg, title, config){
98714         if (Ext.isString(cfg)) {
98715             cfg = {
98716                 title : title,
98717                 msg : cfg,
98718                 closable: false,
98719                 wait: true,
98720                 modal: true,
98721                 minWidth: this.minProgressWidth,
98722                 waitConfig: config
98723             };
98724         }
98725         return this.show(cfg);
98726     },
98727
98728     /**
98729      * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt).
98730      * If a callback function is passed it will be called after the user clicks the button, and the
98731      * id of the button that was clicked will be passed as the only parameter to the callback
98732      * (could also be the top-right close button).
98733      * @param {String} title The title bar text
98734      * @param {String} msg The message box body text
98735      * @param {Function} fn (optional) The callback function invoked after the message box is closed
98736      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
98737      * @return {Ext.window.MessageBox} this
98738      */
98739     alert: function(cfg, msg, fn, scope) {
98740         if (Ext.isString(cfg)) {
98741             cfg = {
98742                 title : cfg,
98743                 msg : msg,
98744                 buttons: this.OK,
98745                 fn: fn,
98746                 scope : scope,
98747                 minWidth: this.minWidth
98748             };
98749         }
98750         return this.show(cfg);
98751     },
98752
98753     /**
98754      * Displays a message box with a progress bar.  This message box has no buttons and is not closeable by
98755      * the user.  You are responsible for updating the progress bar as needed via {@link Ext.window.MessageBox#updateProgress}
98756      * and closing the message box when the process is complete.
98757      * @param {String} title The title bar text
98758      * @param {String} msg The message box body text
98759      * @param {String} [progressText=''] The text to display inside the progress bar
98760      * @return {Ext.window.MessageBox} this
98761      */
98762     progress : function(cfg, msg, progressText){
98763         if (Ext.isString(cfg)) {
98764             cfg = {
98765                 title: cfg,
98766                 msg: msg,
98767                 progress: true,
98768                 progressText: progressText
98769             };
98770         }
98771         return this.show(cfg);
98772     }
98773 }, function() {
98774     /**
98775      * @class Ext.MessageBox
98776      * @alternateClassName Ext.Msg
98777      * @extends Ext.window.MessageBox
98778      * @singleton
98779      * Singleton instance of {@link Ext.window.MessageBox}.
98780      */
98781     Ext.MessageBox = Ext.Msg = new this();
98782 });
98783 /**
98784  * @class Ext.form.Basic
98785  * @extends Ext.util.Observable
98786  *
98787  * Provides input field management, validation, submission, and form loading services for the collection
98788  * of {@link Ext.form.field.Field Field} instances within a {@link Ext.container.Container}. It is recommended
98789  * that you use a {@link Ext.form.Panel} as the form container, as that has logic to automatically
98790  * hook up an instance of {@link Ext.form.Basic} (plus other conveniences related to field configuration.)
98791  *
98792  * ## Form Actions
98793  *
98794  * The Basic class delegates the handling of form loads and submits to instances of {@link Ext.form.action.Action}.
98795  * See the various Action implementations for specific details of each one's functionality, as well as the
98796  * documentation for {@link #doAction} which details the configuration options that can be specified in
98797  * each action call.
98798  *
98799  * The default submit Action is {@link Ext.form.action.Submit}, which uses an Ajax request to submit the
98800  * form's values to a configured URL. To enable normal browser submission of an Ext form, use the
98801  * {@link #standardSubmit} config option.
98802  *
98803  * ## File uploads
98804  *
98805  * File uploads are not performed using normal 'Ajax' techniques; see the description for
98806  * {@link #hasUpload} for details. If you're using file uploads you should read the method description.
98807  *
98808  * ## Example usage:
98809  *
98810  *     Ext.create('Ext.form.Panel', {
98811  *         title: 'Basic Form',
98812  *         renderTo: Ext.getBody(),
98813  *         bodyPadding: 5,
98814  *         width: 350,
98815  *
98816  *         // Any configuration items here will be automatically passed along to
98817  *         // the Ext.form.Basic instance when it gets created.
98818  *
98819  *         // The form will submit an AJAX request to this URL when submitted
98820  *         url: 'save-form.php',
98821  *
98822  *         items: [{
98823  *             fieldLabel: 'Field',
98824  *             name: 'theField'
98825  *         }],
98826  *
98827  *         buttons: [{
98828  *             text: 'Submit',
98829  *             handler: function() {
98830  *                 // The getForm() method returns the Ext.form.Basic instance:
98831  *                 var form = this.up('form').getForm();
98832  *                 if (form.isValid()) {
98833  *                     // Submit the Ajax request and handle the response
98834  *                     form.submit({
98835  *                         success: function(form, action) {
98836  *                            Ext.Msg.alert('Success', action.result.msg);
98837  *                         },
98838  *                         failure: function(form, action) {
98839  *                             Ext.Msg.alert('Failed', action.result.msg);
98840  *                         }
98841  *                     });
98842  *                 }
98843  *             }
98844  *         }]
98845  *     });
98846  *
98847  * @docauthor Jason Johnston <jason@sencha.com>
98848  */
98849 Ext.define('Ext.form.Basic', {
98850     extend: 'Ext.util.Observable',
98851     alternateClassName: 'Ext.form.BasicForm',
98852     requires: ['Ext.util.MixedCollection', 'Ext.form.action.Load', 'Ext.form.action.Submit',
98853                'Ext.window.MessageBox', 'Ext.data.Errors', 'Ext.util.DelayedTask'],
98854
98855     /**
98856      * Creates new form.
98857      * @param {Ext.container.Container} owner The component that is the container for the form, usually a {@link Ext.form.Panel}
98858      * @param {Object} config Configuration options. These are normally specified in the config to the
98859      * {@link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically.
98860      */
98861     constructor: function(owner, config) {
98862         var me = this,
98863             onItemAddOrRemove = me.onItemAddOrRemove;
98864
98865         /**
98866          * @property owner
98867          * @type Ext.container.Container
98868          * The container component to which this BasicForm is attached.
98869          */
98870         me.owner = owner;
98871
98872         // Listen for addition/removal of fields in the owner container
98873         me.mon(owner, {
98874             add: onItemAddOrRemove,
98875             remove: onItemAddOrRemove,
98876             scope: me
98877         });
98878
98879         Ext.apply(me, config);
98880
98881         // Normalize the paramOrder to an Array
98882         if (Ext.isString(me.paramOrder)) {
98883             me.paramOrder = me.paramOrder.split(/[\s,|]/);
98884         }
98885
98886         me.checkValidityTask = Ext.create('Ext.util.DelayedTask', me.checkValidity, me);
98887
98888         me.addEvents(
98889             /**
98890              * @event beforeaction
98891              * Fires before any action is performed. Return false to cancel the action.
98892              * @param {Ext.form.Basic} this
98893              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} to be performed
98894              */
98895             'beforeaction',
98896             /**
98897              * @event actionfailed
98898              * Fires when an action fails.
98899              * @param {Ext.form.Basic} this
98900              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that failed
98901              */
98902             'actionfailed',
98903             /**
98904              * @event actioncomplete
98905              * Fires when an action is completed.
98906              * @param {Ext.form.Basic} this
98907              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that completed
98908              */
98909             'actioncomplete',
98910             /**
98911              * @event validitychange
98912              * Fires when the validity of the entire form changes.
98913              * @param {Ext.form.Basic} this
98914              * @param {Boolean} valid <tt>true</tt> if the form is now valid, <tt>false</tt> if it is now invalid.
98915              */
98916             'validitychange',
98917             /**
98918              * @event dirtychange
98919              * Fires when the dirty state of the entire form changes.
98920              * @param {Ext.form.Basic} this
98921              * @param {Boolean} dirty <tt>true</tt> if the form is now dirty, <tt>false</tt> if it is no longer dirty.
98922              */
98923             'dirtychange'
98924         );
98925         me.callParent();
98926     },
98927
98928     /**
98929      * Do any post constructor initialization
98930      * @private
98931      */
98932     initialize: function(){
98933         this.initialized = true;
98934         this.onValidityChange(!this.hasInvalidField());
98935     },
98936
98937     /**
98938      * @cfg {String} method
98939      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
98940      */
98941
98942     /**
98943      * @cfg {Ext.data.reader.Reader} reader
98944      * An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to read
98945      * data when executing 'load' actions. This is optional as there is built-in
98946      * support for processing JSON responses.
98947      */
98948
98949     /**
98950      * @cfg {Ext.data.reader.Reader} errorReader
98951      * <p>An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to
98952      * read field error messages returned from 'submit' actions. This is optional
98953      * as there is built-in support for processing JSON responses.</p>
98954      * <p>The Records which provide messages for the invalid Fields must use the
98955      * Field name (or id) as the Record ID, and must contain a field called 'msg'
98956      * which contains the error message.</p>
98957      * <p>The errorReader does not have to be a full-blown implementation of a
98958      * Reader. It simply needs to implement a <tt>read(xhr)</tt> function
98959      * which returns an Array of Records in an object with the following
98960      * structure:</p><pre><code>
98961 {
98962     records: recordArray
98963 }
98964 </code></pre>
98965      */
98966
98967     /**
98968      * @cfg {String} url
98969      * The URL to use for form actions if one isn't supplied in the
98970      * {@link #doAction doAction} options.
98971      */
98972
98973     /**
98974      * @cfg {Object} baseParams
98975      * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
98976      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext.Object#toQueryString}.</p>
98977      */
98978
98979     /**
98980      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
98981      */
98982     timeout: 30,
98983
98984     /**
98985      * @cfg {Object} api (Optional) If specified, load and submit actions will be handled
98986      * with {@link Ext.form.action.DirectLoad} and {@link Ext.form.action.DirectLoad}.
98987      * Methods which have been imported by {@link Ext.direct.Manager} can be specified here to load and submit
98988      * forms.
98989      * Such as the following:<pre><code>
98990 api: {
98991     load: App.ss.MyProfile.load,
98992     submit: App.ss.MyProfile.submit
98993 }
98994 </code></pre>
98995      * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
98996      * to customize how the load method is invoked.
98997      * Submit actions will always use a standard form submit. The <tt>formHandler</tt> configuration must
98998      * be set on the associated server-side method which has been imported by {@link Ext.direct.Manager}.</p>
98999      */
99000
99001     /**
99002      * @cfg {String/String[]} paramOrder <p>A list of params to be executed server side.
99003      * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
99004      * <code>load</code> configuration.</p>
99005      * <p>Specify the params in the order in which they must be executed on the
99006      * server-side as either (1) an Array of String values, or (2) a String of params
99007      * delimited by either whitespace, comma, or pipe. For example,
99008      * any of the following would be acceptable:</p><pre><code>
99009 paramOrder: ['param1','param2','param3']
99010 paramOrder: 'param1 param2 param3'
99011 paramOrder: 'param1,param2,param3'
99012 paramOrder: 'param1|param2|param'
99013      </code></pre>
99014      */
99015
99016     /**
99017      * @cfg {Boolean} paramsAsHash
99018      * Only used for the <code>{@link #api}</code>
99019      * <code>load</code> configuration. If <tt>true</tt>, parameters will be sent as a
99020      * single hash collection of named arguments. Providing a
99021      * <tt>{@link #paramOrder}</tt> nullifies this configuration.
99022      */
99023     paramsAsHash: false,
99024
99025     /**
99026      * @cfg {String} waitTitle
99027      * The default title to show for the waiting message box
99028      */
99029     waitTitle: 'Please Wait...',
99030
99031     /**
99032      * @cfg {Boolean} trackResetOnLoad
99033      * If set to true, {@link #reset}() resets to the last loaded or {@link #setValues}() data instead of
99034      * when the form was first created.
99035      */
99036     trackResetOnLoad: false,
99037
99038     /**
99039      * @cfg {Boolean} standardSubmit
99040      * If set to true, a standard HTML form submit is used instead of a XHR (Ajax) style form submission.
99041      * All of the field values, plus any additional params configured via {@link #baseParams}
99042      * and/or the `options` to {@link #submit}, will be included in the values submitted in the form.
99043      */
99044
99045     /**
99046      * @cfg {String/HTMLElement/Ext.Element} waitMsgTarget
99047      * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
99048      * element by passing it or its id or mask the form itself by passing in true.
99049      */
99050
99051
99052     // Private
99053     wasDirty: false,
99054
99055
99056     /**
99057      * Destroys this object.
99058      */
99059     destroy: function() {
99060         this.clearListeners();
99061         this.checkValidityTask.cancel();
99062     },
99063
99064     /**
99065      * @private
99066      * Handle addition or removal of descendant items. Invalidates the cached list of fields
99067      * so that {@link #getFields} will do a fresh query next time it is called. Also adds listeners
99068      * for state change events on added fields, and tracks components with formBind=true.
99069      */
99070     onItemAddOrRemove: function(parent, child) {
99071         var me = this,
99072             isAdding = !!child.ownerCt,
99073             isContainer = child.isContainer;
99074
99075         function handleField(field) {
99076             // Listen for state change events on fields
99077             me[isAdding ? 'mon' : 'mun'](field, {
99078                 validitychange: me.checkValidity,
99079                 dirtychange: me.checkDirty,
99080                 scope: me,
99081                 buffer: 100 //batch up sequential calls to avoid excessive full-form validation
99082             });
99083             // Flush the cached list of fields
99084             delete me._fields;
99085         }
99086
99087         if (child.isFormField) {
99088             handleField(child);
99089         } else if (isContainer) {
99090             // Walk down
99091             if (child.isDestroyed) {
99092                 // the container is destroyed, this means we may have child fields, so here
99093                 // we just invalidate all the fields to be sure.
99094                 delete me._fields;
99095             } else {
99096                 Ext.Array.forEach(child.query('[isFormField]'), handleField);
99097             }
99098         }
99099
99100         // Flush the cached list of formBind components
99101         delete this._boundItems;
99102
99103         // Check form bind, but only after initial add. Batch it to prevent excessive validation
99104         // calls when many fields are being added at once.
99105         if (me.initialized) {
99106             me.checkValidityTask.delay(10);
99107         }
99108     },
99109
99110     /**
99111      * Return all the {@link Ext.form.field.Field} components in the owner container.
99112      * @return {Ext.util.MixedCollection} Collection of the Field objects
99113      */
99114     getFields: function() {
99115         var fields = this._fields;
99116         if (!fields) {
99117             fields = this._fields = Ext.create('Ext.util.MixedCollection');
99118             fields.addAll(this.owner.query('[isFormField]'));
99119         }
99120         return fields;
99121     },
99122
99123     /**
99124      * @private
99125      * Finds and returns the set of all items bound to fields inside this form
99126      * @return {Ext.util.MixedCollection} The set of all bound form field items
99127      */
99128     getBoundItems: function() {
99129         var boundItems = this._boundItems;
99130         
99131         if (!boundItems || boundItems.getCount() === 0) {
99132             boundItems = this._boundItems = Ext.create('Ext.util.MixedCollection');
99133             boundItems.addAll(this.owner.query('[formBind]'));
99134         }
99135         
99136         return boundItems;
99137     },
99138
99139     /**
99140      * Returns true if the form contains any invalid fields. No fields will be marked as invalid
99141      * as a result of calling this; to trigger marking of fields use {@link #isValid} instead.
99142      */
99143     hasInvalidField: function() {
99144         return !!this.getFields().findBy(function(field) {
99145             var preventMark = field.preventMark,
99146                 isValid;
99147             field.preventMark = true;
99148             isValid = field.isValid();
99149             field.preventMark = preventMark;
99150             return !isValid;
99151         });
99152     },
99153
99154     /**
99155      * Returns true if client-side validation on the form is successful. Any invalid fields will be
99156      * marked as invalid. If you only want to determine overall form validity without marking anything,
99157      * use {@link #hasInvalidField} instead.
99158      * @return Boolean
99159      */
99160     isValid: function() {
99161         var me = this,
99162             invalid;
99163         me.batchLayouts(function() {
99164             invalid = me.getFields().filterBy(function(field) {
99165                 return !field.validate();
99166             });
99167         });
99168         return invalid.length < 1;
99169     },
99170
99171     /**
99172      * Check whether the validity of the entire form has changed since it was last checked, and
99173      * if so fire the {@link #validitychange validitychange} event. This is automatically invoked
99174      * when an individual field's validity changes.
99175      */
99176     checkValidity: function() {
99177         var me = this,
99178             valid = !me.hasInvalidField();
99179         if (valid !== me.wasValid) {
99180             me.onValidityChange(valid);
99181             me.fireEvent('validitychange', me, valid);
99182             me.wasValid = valid;
99183         }
99184     },
99185
99186     /**
99187      * @private
99188      * Handle changes in the form's validity. If there are any sub components with
99189      * formBind=true then they are enabled/disabled based on the new validity.
99190      * @param {Boolean} valid
99191      */
99192     onValidityChange: function(valid) {
99193         var boundItems = this.getBoundItems();
99194         if (boundItems) {
99195             boundItems.each(function(cmp) {
99196                 if (cmp.disabled === valid) {
99197                     cmp.setDisabled(!valid);
99198                 }
99199             });
99200         }
99201     },
99202
99203     /**
99204      * <p>Returns true if any fields in this form have changed from their original values.</p>
99205      * <p>Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
99206      * Fields' <em>original values</em> are updated when the values are loaded by {@link #setValues}
99207      * or {@link #loadRecord}.</p>
99208      * @return Boolean
99209      */
99210     isDirty: function() {
99211         return !!this.getFields().findBy(function(f) {
99212             return f.isDirty();
99213         });
99214     },
99215
99216     /**
99217      * Check whether the dirty state of the entire form has changed since it was last checked, and
99218      * if so fire the {@link #dirtychange dirtychange} event. This is automatically invoked
99219      * when an individual field's dirty state changes.
99220      */
99221     checkDirty: function() {
99222         var dirty = this.isDirty();
99223         if (dirty !== this.wasDirty) {
99224             this.fireEvent('dirtychange', this, dirty);
99225             this.wasDirty = dirty;
99226         }
99227     },
99228
99229     /**
99230      * <p>Returns true if the form contains a file upload field. This is used to determine the
99231      * method for submitting the form: File uploads are not performed using normal 'Ajax' techniques,
99232      * that is they are <b>not</b> performed using XMLHttpRequests. Instead a hidden <tt>&lt;form></tt>
99233      * element containing all the fields is created temporarily and submitted with its
99234      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
99235      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
99236      * but removed after the return data has been gathered.</p>
99237      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
99238      * server is using JSON to send the return object, then the
99239      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
99240      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
99241      * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
99242      * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
99243      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
99244      * is created containing a <tt>responseText</tt> property in order to conform to the
99245      * requirements of event handlers and callbacks.</p>
99246      * <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>
99247      * and some server technologies (notably JEE) may require some custom processing in order to
99248      * retrieve parameter names and parameter values from the packet content.</p>
99249      * @return Boolean
99250      */
99251     hasUpload: function() {
99252         return !!this.getFields().findBy(function(f) {
99253             return f.isFileUpload();
99254         });
99255     },
99256
99257     /**
99258      * Performs a predefined action (an implementation of {@link Ext.form.action.Action})
99259      * to perform application-specific processing.
99260      * @param {String/Ext.form.action.Action} action The name of the predefined action type,
99261      * or instance of {@link Ext.form.action.Action} to perform.
99262      * @param {Object} options (optional) The options to pass to the {@link Ext.form.action.Action}
99263      * that will get created, if the <tt>action</tt> argument is a String.
99264      * <p>All of the config options listed below are supported by both the
99265      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
99266      * actions unless otherwise noted (custom actions could also accept
99267      * other config options):</p><ul>
99268      *
99269      * <li><b>url</b> : String<div class="sub-desc">The url for the action (defaults
99270      * to the form's {@link #url}.)</div></li>
99271      *
99272      * <li><b>method</b> : String<div class="sub-desc">The form method to use (defaults
99273      * to the form's method, or POST if not defined)</div></li>
99274      *
99275      * <li><b>params</b> : String/Object<div class="sub-desc"><p>The params to pass
99276      * (defaults to the form's baseParams, or none if not defined)</p>
99277      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p></div></li>
99278      *
99279      * <li><b>headers</b> : Object<div class="sub-desc">Request headers to set for the action.</div></li>
99280      *
99281      * <li><b>success</b> : Function<div class="sub-desc">The callback that will
99282      * be invoked after a successful response (see top of
99283      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
99284      * for a description of what constitutes a successful response).
99285      * The function is passed the following parameters:<ul>
99286      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
99287      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
99288      * <div class="sub-desc">The action object contains these properties of interest:<ul>
99289      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
99290      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
99291      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
99292      * </ul></div></li></ul></div></li>
99293      *
99294      * <li><b>failure</b> : Function<div class="sub-desc">The callback that will be invoked after a
99295      * failed transaction attempt. The function is passed the following parameters:<ul>
99296      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
99297      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
99298      * <div class="sub-desc">The action object contains these properties of interest:<ul>
99299      * <li><tt>{@link Ext.form.action.Action#failureType failureType}</tt></li>
99300      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
99301      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
99302      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
99303      * </ul></div></li></ul></div></li>
99304      *
99305      * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the
99306      * callback functions (The <tt>this</tt> reference for the callback functions).</div></li>
99307      *
99308      * <li><b>clientValidation</b> : Boolean<div class="sub-desc">Submit Action only.
99309      * Determines whether a Form's fields are validated in a final call to
99310      * {@link Ext.form.Basic#isValid isValid} prior to submission. Set to <tt>false</tt>
99311      * to prevent this. If undefined, pre-submission field validation is performed.</div></li></ul>
99312      *
99313      * @return {Ext.form.Basic} this
99314      */
99315     doAction: function(action, options) {
99316         if (Ext.isString(action)) {
99317             action = Ext.ClassManager.instantiateByAlias('formaction.' + action, Ext.apply({}, options, {form: this}));
99318         }
99319         if (this.fireEvent('beforeaction', this, action) !== false) {
99320             this.beforeAction(action);
99321             Ext.defer(action.run, 100, action);
99322         }
99323         return this;
99324     },
99325
99326     /**
99327      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Submit submit action}. This will use the
99328      * {@link Ext.form.action.Submit AJAX submit action} by default. If the {@link #standardSubmit} config is
99329      * enabled it will use a standard form element to submit, or if the {@link #api} config is present it will
99330      * use the {@link Ext.form.action.DirectLoad Ext.direct.Direct submit action}.
99331      * @param {Object} options The options to pass to the action (see {@link #doAction} for details).<br>
99332      * <p>The following code:</p><pre><code>
99333 myFormPanel.getForm().submit({
99334     clientValidation: true,
99335     url: 'updateConsignment.php',
99336     params: {
99337         newStatus: 'delivered'
99338     },
99339     success: function(form, action) {
99340        Ext.Msg.alert('Success', action.result.msg);
99341     },
99342     failure: function(form, action) {
99343         switch (action.failureType) {
99344             case Ext.form.action.Action.CLIENT_INVALID:
99345                 Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
99346                 break;
99347             case Ext.form.action.Action.CONNECT_FAILURE:
99348                 Ext.Msg.alert('Failure', 'Ajax communication failed');
99349                 break;
99350             case Ext.form.action.Action.SERVER_INVALID:
99351                Ext.Msg.alert('Failure', action.result.msg);
99352        }
99353     }
99354 });
99355 </code></pre>
99356      * would process the following server response for a successful submission:<pre><code>
99357 {
99358     "success":true, // note this is Boolean, not string
99359     "msg":"Consignment updated"
99360 }
99361 </code></pre>
99362      * and the following server response for a failed submission:<pre><code>
99363 {
99364     "success":false, // note this is Boolean, not string
99365     "msg":"You do not have permission to perform this operation"
99366 }
99367 </code></pre>
99368      * @return {Ext.form.Basic} this
99369      */
99370     submit: function(options) {
99371         return this.doAction(this.standardSubmit ? 'standardsubmit' : this.api ? 'directsubmit' : 'submit', options);
99372     },
99373
99374     /**
99375      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Load load action}.
99376      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
99377      * @return {Ext.form.Basic} this
99378      */
99379     load: function(options) {
99380         return this.doAction(this.api ? 'directload' : 'load', options);
99381     },
99382
99383     /**
99384      * Persists the values in this form into the passed {@link Ext.data.Model} object in a beginEdit/endEdit block.
99385      * @param {Ext.data.Model} record The record to edit
99386      * @return {Ext.form.Basic} this
99387      */
99388     updateRecord: function(record) {
99389         var fields = record.fields,
99390             values = this.getFieldValues(),
99391             name,
99392             obj = {};
99393
99394         fields.each(function(f) {
99395             name = f.name;
99396             if (name in values) {
99397                 obj[name] = values[name];
99398             }
99399         });
99400
99401         record.beginEdit();
99402         record.set(obj);
99403         record.endEdit();
99404
99405         return this;
99406     },
99407
99408     /**
99409      * Loads an {@link Ext.data.Model} into this form by calling {@link #setValues} with the
99410      * {@link Ext.data.Model#raw record data}.
99411      * See also {@link #trackResetOnLoad}.
99412      * @param {Ext.data.Model} record The record to load
99413      * @return {Ext.form.Basic} this
99414      */
99415     loadRecord: function(record) {
99416         this._record = record;
99417         return this.setValues(record.data);
99418     },
99419
99420     /**
99421      * Returns the last Ext.data.Model instance that was loaded via {@link #loadRecord}
99422      * @return {Ext.data.Model} The record
99423      */
99424     getRecord: function() {
99425         return this._record;
99426     },
99427
99428     /**
99429      * @private
99430      * Called before an action is performed via {@link #doAction}.
99431      * @param {Ext.form.action.Action} action The Action instance that was invoked
99432      */
99433     beforeAction: function(action) {
99434         var waitMsg = action.waitMsg,
99435             maskCls = Ext.baseCSSPrefix + 'mask-loading',
99436             waitMsgTarget;
99437
99438         // Call HtmlEditor's syncValue before actions
99439         this.getFields().each(function(f) {
99440             if (f.isFormField && f.syncValue) {
99441                 f.syncValue();
99442             }
99443         });
99444
99445         if (waitMsg) {
99446             waitMsgTarget = this.waitMsgTarget;
99447             if (waitMsgTarget === true) {
99448                 this.owner.el.mask(waitMsg, maskCls);
99449             } else if (waitMsgTarget) {
99450                 waitMsgTarget = this.waitMsgTarget = Ext.get(waitMsgTarget);
99451                 waitMsgTarget.mask(waitMsg, maskCls);
99452             } else {
99453                 Ext.MessageBox.wait(waitMsg, action.waitTitle || this.waitTitle);
99454             }
99455         }
99456     },
99457
99458     /**
99459      * @private
99460      * Called after an action is performed via {@link #doAction}.
99461      * @param {Ext.form.action.Action} action The Action instance that was invoked
99462      * @param {Boolean} success True if the action completed successfully, false, otherwise.
99463      */
99464     afterAction: function(action, success) {
99465         if (action.waitMsg) {
99466             var MessageBox = Ext.MessageBox,
99467                 waitMsgTarget = this.waitMsgTarget;
99468             if (waitMsgTarget === true) {
99469                 this.owner.el.unmask();
99470             } else if (waitMsgTarget) {
99471                 waitMsgTarget.unmask();
99472             } else {
99473                 MessageBox.updateProgress(1);
99474                 MessageBox.hide();
99475             }
99476         }
99477         if (success) {
99478             if (action.reset) {
99479                 this.reset();
99480             }
99481             Ext.callback(action.success, action.scope || action, [this, action]);
99482             this.fireEvent('actioncomplete', this, action);
99483         } else {
99484             Ext.callback(action.failure, action.scope || action, [this, action]);
99485             this.fireEvent('actionfailed', this, action);
99486         }
99487     },
99488
99489
99490     /**
99491      * Find a specific {@link Ext.form.field.Field} in this form by id or name.
99492      * @param {String} id The value to search for (specify either a {@link Ext.Component#id id} or
99493      * {@link Ext.form.field.Field#getName name or hiddenName}).
99494      * @return Ext.form.field.Field The first matching field, or <tt>null</tt> if none was found.
99495      */
99496     findField: function(id) {
99497         return this.getFields().findBy(function(f) {
99498             return f.id === id || f.getName() === id;
99499         });
99500     },
99501
99502
99503     /**
99504      * Mark fields in this form invalid in bulk.
99505      * @param {Object/Object[]/Ext.data.Errors} errors
99506      * Either an array in the form <code>[{id:'fieldId', msg:'The message'}, ...]</code>,
99507      * an object hash of <code>{id: msg, id2: msg2}</code>, or a {@link Ext.data.Errors} object.
99508      * @return {Ext.form.Basic} this
99509      */
99510     markInvalid: function(errors) {
99511         var me = this;
99512
99513         function mark(fieldId, msg) {
99514             var field = me.findField(fieldId);
99515             if (field) {
99516                 field.markInvalid(msg);
99517             }
99518         }
99519
99520         if (Ext.isArray(errors)) {
99521             Ext.each(errors, function(err) {
99522                 mark(err.id, err.msg);
99523             });
99524         }
99525         else if (errors instanceof Ext.data.Errors) {
99526             errors.each(function(err) {
99527                 mark(err.field, err.message);
99528             });
99529         }
99530         else {
99531             Ext.iterate(errors, mark);
99532         }
99533         return this;
99534     },
99535
99536     /**
99537      * Set values for fields in this form in bulk.
99538      * @param {Object/Object[]} values Either an array in the form:<pre><code>
99539 [{id:'clientName', value:'Fred. Olsen Lines'},
99540  {id:'portOfLoading', value:'FXT'},
99541  {id:'portOfDischarge', value:'OSL'} ]</code></pre>
99542      * or an object hash of the form:<pre><code>
99543 {
99544     clientName: 'Fred. Olsen Lines',
99545     portOfLoading: 'FXT',
99546     portOfDischarge: 'OSL'
99547 }</code></pre>
99548      * @return {Ext.form.Basic} this
99549      */
99550     setValues: function(values) {
99551         var me = this;
99552
99553         function setVal(fieldId, val) {
99554             var field = me.findField(fieldId);
99555             if (field) {
99556                 field.setValue(val);
99557                 if (me.trackResetOnLoad) {
99558                     field.resetOriginalValue();
99559                 }
99560             }
99561         }
99562
99563         if (Ext.isArray(values)) {
99564             // array of objects
99565             Ext.each(values, function(val) {
99566                 setVal(val.id, val.value);
99567             });
99568         } else {
99569             // object hash
99570             Ext.iterate(values, setVal);
99571         }
99572         return this;
99573     },
99574
99575     /**
99576      * Retrieves the fields in the form as a set of key/value pairs, using their
99577      * {@link Ext.form.field.Field#getSubmitData getSubmitData()} method to collect the values.
99578      * If multiple fields return values under the same name those values will be combined into an Array.
99579      * This is similar to {@link #getFieldValues} except that this method collects only String values for
99580      * submission, while getFieldValues collects type-specific data values (e.g. Date objects for date fields.)
99581      * @param {Boolean} asString (optional) If true, will return the key/value collection as a single
99582      * URL-encoded param string. Defaults to false.
99583      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
99584      * Defaults to false.
99585      * @param {Boolean} includeEmptyText (optional) If true, the configured emptyText of empty fields will be used.
99586      * Defaults to false.
99587      * @return {String/Object}
99588      */
99589     getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
99590         var values = {};
99591
99592         this.getFields().each(function(field) {
99593             if (!dirtyOnly || field.isDirty()) {
99594                 var data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);
99595                 if (Ext.isObject(data)) {
99596                     Ext.iterate(data, function(name, val) {
99597                         if (includeEmptyText && val === '') {
99598                             val = field.emptyText || '';
99599                         }
99600                         if (name in values) {
99601                             var bucket = values[name],
99602                                 isArray = Ext.isArray;
99603                             if (!isArray(bucket)) {
99604                                 bucket = values[name] = [bucket];
99605                             }
99606                             if (isArray(val)) {
99607                                 values[name] = bucket.concat(val);
99608                             } else {
99609                                 bucket.push(val);
99610                             }
99611                         } else {
99612                             values[name] = val;
99613                         }
99614                     });
99615                 }
99616             }
99617         });
99618
99619         if (asString) {
99620             values = Ext.Object.toQueryString(values);
99621         }
99622         return values;
99623     },
99624
99625     /**
99626      * Retrieves the fields in the form as a set of key/value pairs, using their
99627      * {@link Ext.form.field.Field#getModelData getModelData()} method to collect the values.
99628      * If multiple fields return values under the same name those values will be combined into an Array.
99629      * This is similar to {@link #getValues} except that this method collects type-specific data values
99630      * (e.g. Date objects for date fields) while getValues returns only String values for submission.
99631      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
99632      * Defaults to false.
99633      * @return {Object}
99634      */
99635     getFieldValues: function(dirtyOnly) {
99636         return this.getValues(false, dirtyOnly, false, true);
99637     },
99638
99639     /**
99640      * Clears all invalid field messages in this form.
99641      * @return {Ext.form.Basic} this
99642      */
99643     clearInvalid: function() {
99644         var me = this;
99645         me.batchLayouts(function() {
99646             me.getFields().each(function(f) {
99647                 f.clearInvalid();
99648             });
99649         });
99650         return me;
99651     },
99652
99653     /**
99654      * Resets all fields in this form.
99655      * @return {Ext.form.Basic} this
99656      */
99657     reset: function() {
99658         var me = this;
99659         me.batchLayouts(function() {
99660             me.getFields().each(function(f) {
99661                 f.reset();
99662             });
99663         });
99664         return me;
99665     },
99666
99667     /**
99668      * Calls {@link Ext#apply Ext.apply} for all fields in this form with the passed object.
99669      * @param {Object} obj The object to be applied
99670      * @return {Ext.form.Basic} this
99671      */
99672     applyToFields: function(obj) {
99673         this.getFields().each(function(f) {
99674             Ext.apply(f, obj);
99675         });
99676         return this;
99677     },
99678
99679     /**
99680      * Calls {@link Ext#applyIf Ext.applyIf} for all field in this form with the passed object.
99681      * @param {Object} obj The object to be applied
99682      * @return {Ext.form.Basic} this
99683      */
99684     applyIfToFields: function(obj) {
99685         this.getFields().each(function(f) {
99686             Ext.applyIf(f, obj);
99687         });
99688         return this;
99689     },
99690
99691     /**
99692      * @private
99693      * Utility wrapper that suspends layouts of all field parent containers for the duration of a given
99694      * function. Used during full-form validation and resets to prevent huge numbers of layouts.
99695      * @param {Function} fn
99696      */
99697     batchLayouts: function(fn) {
99698         var me = this,
99699             suspended = new Ext.util.HashMap();
99700
99701         // Temporarily suspend layout on each field's immediate owner so we don't get a huge layout cascade
99702         me.getFields().each(function(field) {
99703             var ownerCt = field.ownerCt;
99704             if (!suspended.contains(ownerCt)) {
99705                 suspended.add(ownerCt);
99706                 ownerCt.oldSuspendLayout = ownerCt.suspendLayout;
99707                 ownerCt.suspendLayout = true;
99708             }
99709         });
99710
99711         // Invoke the function
99712         fn();
99713
99714         // Un-suspend the container layouts
99715         suspended.each(function(id, ct) {
99716             ct.suspendLayout = ct.oldSuspendLayout;
99717             delete ct.oldSuspendLayout;
99718         });
99719
99720         // Trigger a single layout
99721         me.owner.doComponentLayout();
99722     }
99723 });
99724
99725 /**
99726  * @class Ext.form.FieldAncestor
99727
99728 A mixin for {@link Ext.container.Container} components that are likely to have form fields in their
99729 items subtree. Adds the following capabilities:
99730
99731 - Methods for handling the addition and removal of {@link Ext.form.Labelable} and {@link Ext.form.field.Field}
99732   instances at any depth within the container.
99733 - Events ({@link #fieldvaliditychange} and {@link #fielderrorchange}) for handling changes to the state
99734   of individual fields at the container level.
99735 - Automatic application of {@link #fieldDefaults} config properties to each field added within the
99736   container, to facilitate uniform configuration of all fields.
99737
99738 This mixin is primarily for internal use by {@link Ext.form.Panel} and {@link Ext.form.FieldContainer},
99739 and should not normally need to be used directly.
99740
99741  * @markdown
99742  * @docauthor Jason Johnston <jason@sencha.com>
99743  */
99744 Ext.define('Ext.form.FieldAncestor', {
99745
99746     /**
99747      * @cfg {Object} fieldDefaults
99748      * <p>If specified, the properties in this object are used as default config values for each
99749      * {@link Ext.form.Labelable} instance (e.g. {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer})
99750      * that is added as a descendant of this container. Corresponding values specified in an individual field's
99751      * own configuration, or from the {@link Ext.container.Container#defaults defaults config} of its parent container,
99752      * will take precedence. See the documentation for {@link Ext.form.Labelable} to see what config
99753      * options may be specified in the <tt>fieldDefaults</tt>.</p>
99754      * <p>Example:</p>
99755      * <pre><code>new Ext.form.Panel({
99756     fieldDefaults: {
99757         labelAlign: 'left',
99758         labelWidth: 100
99759     },
99760     items: [{
99761         xtype: 'fieldset',
99762         defaults: {
99763             labelAlign: 'top'
99764         },
99765         items: [{
99766             name: 'field1'
99767         }, {
99768             name: 'field2'
99769         }]
99770     }, {
99771         xtype: 'fieldset',
99772         items: [{
99773             name: 'field3',
99774             labelWidth: 150
99775         }, {
99776             name: 'field4'
99777         }]
99778     }]
99779 });</code></pre>
99780      * <p>In this example, field1 and field2 will get labelAlign:'top' (from the fieldset's <tt>defaults</tt>)
99781      * and labelWidth:100 (from <tt>fieldDefaults</tt>), field3 and field4 will both get labelAlign:'left' (from
99782      * <tt>fieldDefaults</tt> and field3 will use the labelWidth:150 from its own config.</p>
99783      */
99784
99785
99786     /**
99787      * @protected Initializes the FieldAncestor's state; this must be called from the initComponent method
99788      * of any components importing this mixin.
99789      */
99790     initFieldAncestor: function() {
99791         var me = this,
99792             onSubtreeChange = me.onFieldAncestorSubtreeChange;
99793
99794         me.addEvents(
99795             /**
99796              * @event fieldvaliditychange
99797              * Fires when the validity state of any one of the {@link Ext.form.field.Field} instances within this
99798              * container changes.
99799              * @param {Ext.form.FieldAncestor} this
99800              * @param {Ext.form.Labelable} The Field instance whose validity changed
99801              * @param {String} isValid The field's new validity state
99802              */
99803             'fieldvaliditychange',
99804
99805             /**
99806              * @event fielderrorchange
99807              * Fires when the active error message is changed for any one of the {@link Ext.form.Labelable}
99808              * instances within this container.
99809              * @param {Ext.form.FieldAncestor} this
99810              * @param {Ext.form.Labelable} The Labelable instance whose active error was changed
99811              * @param {String} error The active error message
99812              */
99813             'fielderrorchange'
99814         );
99815
99816         // Catch addition and removal of descendant fields
99817         me.on('add', onSubtreeChange, me);
99818         me.on('remove', onSubtreeChange, me);
99819
99820         me.initFieldDefaults();
99821     },
99822
99823     /**
99824      * @private Initialize the {@link #fieldDefaults} object
99825      */
99826     initFieldDefaults: function() {
99827         if (!this.fieldDefaults) {
99828             this.fieldDefaults = {};
99829         }
99830     },
99831
99832     /**
99833      * @private
99834      * Handle the addition and removal of components in the FieldAncestor component's child tree.
99835      */
99836     onFieldAncestorSubtreeChange: function(parent, child) {
99837         var me = this,
99838             isAdding = !!child.ownerCt;
99839
99840         function handleCmp(cmp) {
99841             var isLabelable = cmp.isFieldLabelable,
99842                 isField = cmp.isFormField;
99843             if (isLabelable || isField) {
99844                 if (isLabelable) {
99845                     me['onLabelable' + (isAdding ? 'Added' : 'Removed')](cmp);
99846                 }
99847                 if (isField) {
99848                     me['onField' + (isAdding ? 'Added' : 'Removed')](cmp);
99849                 }
99850             }
99851             else if (cmp.isContainer) {
99852                 Ext.Array.forEach(cmp.getRefItems(), handleCmp);
99853             }
99854         }
99855         handleCmp(child);
99856     },
99857
99858     /**
99859      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
99860      * @param {Ext.form.Labelable} labelable The instance that was added
99861      */
99862     onLabelableAdded: function(labelable) {
99863         var me = this;
99864
99865         // buffer slightly to avoid excessive firing while sub-fields are changing en masse
99866         me.mon(labelable, 'errorchange', me.handleFieldErrorChange, me, {buffer: 10});
99867
99868         labelable.setFieldDefaults(me.fieldDefaults);
99869     },
99870
99871     /**
99872      * @protected Called when a {@link Ext.form.field.Field} instance is added to the container's subtree.
99873      * @param {Ext.form.field.Field} field The field which was added
99874      */
99875     onFieldAdded: function(field) {
99876         var me = this;
99877         me.mon(field, 'validitychange', me.handleFieldValidityChange, me);
99878     },
99879
99880     /**
99881      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
99882      * @param {Ext.form.Labelable} labelable The instance that was removed
99883      */
99884     onLabelableRemoved: function(labelable) {
99885         var me = this;
99886         me.mun(labelable, 'errorchange', me.handleFieldErrorChange, me);
99887     },
99888
99889     /**
99890      * @protected Called when a {@link Ext.form.field.Field} instance is removed from the container's subtree.
99891      * @param {Ext.form.field.Field} field The field which was removed
99892      */
99893     onFieldRemoved: function(field) {
99894         var me = this;
99895         me.mun(field, 'validitychange', me.handleFieldValidityChange, me);
99896     },
99897
99898     /**
99899      * @private Handle validitychange events on sub-fields; invoke the aggregated event and method
99900      */
99901     handleFieldValidityChange: function(field, isValid) {
99902         var me = this;
99903         me.fireEvent('fieldvaliditychange', me, field, isValid);
99904         me.onFieldValidityChange();
99905     },
99906
99907     /**
99908      * @private Handle errorchange events on sub-fields; invoke the aggregated event and method
99909      */
99910     handleFieldErrorChange: function(labelable, activeError) {
99911         var me = this;
99912         me.fireEvent('fielderrorchange', me, labelable, activeError);
99913         me.onFieldErrorChange();
99914     },
99915
99916     /**
99917      * @protected Fired when the validity of any field within the container changes.
99918      * @param {Ext.form.field.Field} The sub-field whose validity changed
99919      * @param {String} The new validity state
99920      */
99921     onFieldValidityChange: Ext.emptyFn,
99922
99923     /**
99924      * @protected Fired when the error message of any field within the container changes.
99925      * @param {Ext.form.Labelable} The sub-field whose active error changed
99926      * @param {String} The new active error message
99927      */
99928     onFieldErrorChange: Ext.emptyFn
99929
99930 });
99931 /**
99932  * @class Ext.layout.container.CheckboxGroup
99933  * @extends Ext.layout.container.Container
99934  * <p>This layout implements the column arrangement for {@link Ext.form.CheckboxGroup} and {@link Ext.form.RadioGroup}.
99935  * It groups the component's sub-items into columns based on the component's
99936  * {@link Ext.form.CheckboxGroup#columns columns} and {@link Ext.form.CheckboxGroup#vertical} config properties.</p>
99937  *
99938  */
99939 Ext.define('Ext.layout.container.CheckboxGroup', {
99940     extend: 'Ext.layout.container.Container',
99941     alias: ['layout.checkboxgroup'],
99942
99943
99944     onLayout: function() {
99945         var numCols = this.getColCount(),
99946             shadowCt = this.getShadowCt(),
99947             owner = this.owner,
99948             items = owner.items,
99949             shadowItems = shadowCt.items,
99950             numItems = items.length,
99951             colIndex = 0,
99952             i, numRows;
99953
99954         // Distribute the items into the appropriate column containers. We add directly to the
99955         // containers' items collection rather than calling container.add(), because we need the
99956         // checkboxes to maintain their original ownerCt. The distribution is done on each layout
99957         // in case items have been added, removed, or reordered.
99958
99959         shadowItems.each(function(col) {
99960             col.items.clear();
99961         });
99962
99963         // If columns="auto", then the number of required columns may change as checkboxes are added/removed
99964         // from the CheckboxGroup; adjust to match.
99965         while (shadowItems.length > numCols) {
99966             shadowCt.remove(shadowItems.last());
99967         }
99968         while (shadowItems.length < numCols) {
99969             shadowCt.add({
99970                 xtype: 'container',
99971                 cls: owner.groupCls,
99972                 flex: 1
99973             });
99974         }
99975
99976         if (owner.vertical) {
99977             numRows = Math.ceil(numItems / numCols);
99978             for (i = 0; i < numItems; i++) {
99979                 if (i > 0 && i % numRows === 0) {
99980                     colIndex++;
99981                 }
99982                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
99983             }
99984         } else {
99985             for (i = 0; i < numItems; i++) {
99986                 colIndex = i % numCols;
99987                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
99988             }
99989         }
99990
99991         if (!shadowCt.rendered) {
99992             shadowCt.render(this.getRenderTarget());
99993         } else {
99994             // Ensure all items are rendered in the correct place in the correct column - this won't
99995             // get done by the column containers themselves if their dimensions are not changing.
99996             shadowItems.each(function(col) {
99997                 var layout = col.getLayout();
99998                 layout.renderItems(layout.getLayoutItems(), layout.getRenderTarget());
99999             });
100000         }
100001
100002         shadowCt.doComponentLayout();
100003     },
100004
100005
100006     // We don't want to render any items to the owner directly, that gets handled by each column's own layout
100007     renderItems: Ext.emptyFn,
100008
100009
100010     /**
100011      * @private
100012      * Creates and returns the shadow hbox container that will be used to arrange the owner's items
100013      * into columns.
100014      */
100015     getShadowCt: function() {
100016         var me = this,
100017             shadowCt = me.shadowCt,
100018             owner, items, item, columns, columnsIsArray, numCols, i;
100019
100020         if (!shadowCt) {
100021             // Create the column containers based on the owner's 'columns' config
100022             owner = me.owner;
100023             columns = owner.columns;
100024             columnsIsArray = Ext.isArray(columns);
100025             numCols = me.getColCount();
100026             items = [];
100027             for(i = 0; i < numCols; i++) {
100028                 item = {
100029                     xtype: 'container',
100030                     cls: owner.groupCls
100031                 };
100032                 if (columnsIsArray) {
100033                     // Array can contain mixture of whole numbers, used as fixed pixel widths, and fractional
100034                     // numbers, used as relative flex values.
100035                     if (columns[i] < 1) {
100036                         item.flex = columns[i];
100037                     } else {
100038                         item.width = columns[i];
100039                     }
100040                 }
100041                 else {
100042                     // All columns the same width
100043                     item.flex = 1;
100044                 }
100045                 items.push(item);
100046             }
100047
100048             // Create the shadow container; delay rendering until after items are added to the columns
100049             shadowCt = me.shadowCt = Ext.createWidget('container', {
100050                 layout: 'hbox',
100051                 items: items,
100052                 ownerCt: owner
100053             });
100054         }
100055         
100056         return shadowCt;
100057     },
100058
100059
100060     /**
100061      * @private Get the number of columns in the checkbox group
100062      */
100063     getColCount: function() {
100064         var owner = this.owner,
100065             colsCfg = owner.columns;
100066         return Ext.isArray(colsCfg) ? colsCfg.length : (Ext.isNumber(colsCfg) ? colsCfg : owner.items.length);
100067     }
100068
100069 });
100070
100071 /**
100072  * FieldContainer is a derivation of {@link Ext.container.Container Container} that implements the
100073  * {@link Ext.form.Labelable Labelable} mixin. This allows it to be configured so that it is rendered with
100074  * a {@link #fieldLabel field label} and optional {@link #msgTarget error message} around its sub-items.
100075  * This is useful for arranging a group of fields or other components within a single item in a form, so
100076  * that it lines up nicely with other fields. A common use is for grouping a set of related fields under
100077  * a single label in a form.
100078  * 
100079  * The container's configured {@link #items} will be layed out within the field body area according to the
100080  * configured {@link #layout} type. The default layout is `'autocontainer'`.
100081  * 
100082  * Like regular fields, FieldContainer can inherit its decoration configuration from the
100083  * {@link Ext.form.Panel#fieldDefaults fieldDefaults} of an enclosing FormPanel. In addition,
100084  * FieldContainer itself can pass {@link #fieldDefaults} to any {@link Ext.form.Labelable fields}
100085  * it may itself contain.
100086  * 
100087  * If you are grouping a set of {@link Ext.form.field.Checkbox Checkbox} or {@link Ext.form.field.Radio Radio}
100088  * fields in a single labeled container, consider using a {@link Ext.form.CheckboxGroup}
100089  * or {@link Ext.form.RadioGroup} instead as they are specialized for handling those types.
100090  *
100091  * # Example
100092  * 
100093  *     @example
100094  *     Ext.create('Ext.form.Panel', {
100095  *         title: 'FieldContainer Example',
100096  *         width: 550,
100097  *         bodyPadding: 10,
100098  * 
100099  *         items: [{
100100  *             xtype: 'fieldcontainer',
100101  *             fieldLabel: 'Last Three Jobs',
100102  *             labelWidth: 100,
100103  * 
100104  *             // The body area will contain three text fields, arranged
100105  *             // horizontally, separated by draggable splitters.
100106  *             layout: 'hbox',
100107  *             items: [{
100108  *                 xtype: 'textfield',
100109  *                 flex: 1
100110  *             }, {
100111  *                 xtype: 'splitter'
100112  *             }, {
100113  *                 xtype: 'textfield',
100114  *                 flex: 1
100115  *             }, {
100116  *                 xtype: 'splitter'
100117  *             }, {
100118  *                 xtype: 'textfield',
100119  *                 flex: 1
100120  *             }]
100121  *         }],
100122  *         renderTo: Ext.getBody()
100123  *     });
100124  * 
100125  * # Usage of fieldDefaults
100126  *
100127  *     @example
100128  *     Ext.create('Ext.form.Panel', {
100129  *         title: 'FieldContainer Example',
100130  *         width: 350,
100131  *         bodyPadding: 10,
100132  * 
100133  *         items: [{
100134  *             xtype: 'fieldcontainer',
100135  *             fieldLabel: 'Your Name',
100136  *             labelWidth: 75,
100137  *             defaultType: 'textfield',
100138  * 
100139  *             // Arrange fields vertically, stretched to full width
100140  *             layout: 'anchor',
100141  *             defaults: {
100142  *                 layout: '100%'
100143  *             },
100144  * 
100145  *             // These config values will be applied to both sub-fields, except
100146  *             // for Last Name which will use its own msgTarget.
100147  *             fieldDefaults: {
100148  *                 msgTarget: 'under',
100149  *                 labelAlign: 'top'
100150  *             },
100151  * 
100152  *             items: [{
100153  *                 fieldLabel: 'First Name',
100154  *                 name: 'firstName'
100155  *             }, {
100156  *                 fieldLabel: 'Last Name',
100157  *                 name: 'lastName',
100158  *                 msgTarget: 'under'
100159  *             }]
100160  *         }],
100161  *         renderTo: Ext.getBody()
100162  *     });
100163  * 
100164  * @docauthor Jason Johnston <jason@sencha.com>
100165  */
100166 Ext.define('Ext.form.FieldContainer', {
100167     extend: 'Ext.container.Container',
100168     mixins: {
100169         labelable: 'Ext.form.Labelable',
100170         fieldAncestor: 'Ext.form.FieldAncestor'
100171     },
100172     alias: 'widget.fieldcontainer',
100173
100174     componentLayout: 'field',
100175
100176     /**
100177      * @cfg {Boolean} combineLabels
100178      * If set to true, and there is no defined {@link #fieldLabel}, the field container will automatically
100179      * generate its label by combining the labels of all the fields it contains. Defaults to false.
100180      */
100181     combineLabels: false,
100182
100183     /**
100184      * @cfg {String} labelConnector
100185      * The string to use when joining the labels of individual sub-fields, when {@link #combineLabels} is
100186      * set to true. Defaults to ', '.
100187      */
100188     labelConnector: ', ',
100189
100190     /**
100191      * @cfg {Boolean} combineErrors
100192      * If set to true, the field container will automatically combine and display the validation errors from
100193      * all the fields it contains as a single error on the container, according to the configured
100194      * {@link #msgTarget}. Defaults to false.
100195      */
100196     combineErrors: false,
100197
100198     maskOnDisable: false,
100199
100200     initComponent: function() {
100201         var me = this,
100202             onSubCmpAddOrRemove = me.onSubCmpAddOrRemove;
100203
100204         // Init mixins
100205         me.initLabelable();
100206         me.initFieldAncestor();
100207
100208         me.callParent();
100209     },
100210
100211     /**
100212      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
100213      * @param {Ext.form.Labelable} labelable The instance that was added
100214      */
100215     onLabelableAdded: function(labelable) {
100216         var me = this;
100217         me.mixins.fieldAncestor.onLabelableAdded.call(this, labelable);
100218         me.updateLabel();
100219     },
100220
100221     /**
100222      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
100223      * @param {Ext.form.Labelable} labelable The instance that was removed
100224      */
100225     onLabelableRemoved: function(labelable) {
100226         var me = this;
100227         me.mixins.fieldAncestor.onLabelableRemoved.call(this, labelable);
100228         me.updateLabel();
100229     },
100230
100231     onRender: function() {
100232         var me = this;
100233
100234         me.onLabelableRender();
100235
100236         me.callParent(arguments);
100237     },
100238
100239     initRenderTpl: function() {
100240         var me = this;
100241         if (!me.hasOwnProperty('renderTpl')) {
100242             me.renderTpl = me.getTpl('labelableRenderTpl');
100243         }
100244         return me.callParent();
100245     },
100246
100247     initRenderData: function() {
100248         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
100249     },
100250
100251     /**
100252      * Returns the combined field label if {@link #combineLabels} is set to true and if there is no
100253      * set {@link #fieldLabel}. Otherwise returns the fieldLabel like normal. You can also override
100254      * this method to provide a custom generated label.
100255      */
100256     getFieldLabel: function() {
100257         var label = this.fieldLabel || '';
100258         if (!label && this.combineLabels) {
100259             label = Ext.Array.map(this.query('[isFieldLabelable]'), function(field) {
100260                 return field.getFieldLabel();
100261             }).join(this.labelConnector);
100262         }
100263         return label;
100264     },
100265
100266     /**
100267      * @private Updates the content of the labelEl if it is rendered
100268      */
100269     updateLabel: function() {
100270         var me = this,
100271             label = me.labelEl;
100272         if (label) {
100273             label.update(me.getFieldLabel());
100274         }
100275     },
100276
100277
100278     /**
100279      * @private Fired when the error message of any field within the container changes, and updates the
100280      * combined error message to match.
100281      */
100282     onFieldErrorChange: function(field, activeError) {
100283         if (this.combineErrors) {
100284             var me = this,
100285                 oldError = me.getActiveError(),
100286                 invalidFields = Ext.Array.filter(me.query('[isFormField]'), function(field) {
100287                     return field.hasActiveError();
100288                 }),
100289                 newErrors = me.getCombinedErrors(invalidFields);
100290
100291             if (newErrors) {
100292                 me.setActiveErrors(newErrors);
100293             } else {
100294                 me.unsetActiveError();
100295             }
100296
100297             if (oldError !== me.getActiveError()) {
100298                 me.doComponentLayout();
100299             }
100300         }
100301     },
100302
100303     /**
100304      * Takes an Array of invalid {@link Ext.form.field.Field} objects and builds a combined list of error
100305      * messages from them. Defaults to prepending each message by the field name and a colon. This
100306      * can be overridden to provide custom combined error message handling, for instance changing
100307      * the format of each message or sorting the array (it is sorted in order of appearance by default).
100308      * @param {Ext.form.field.Field[]} invalidFields An Array of the sub-fields which are currently invalid.
100309      * @return {String[]} The combined list of error messages
100310      */
100311     getCombinedErrors: function(invalidFields) {
100312         var forEach = Ext.Array.forEach,
100313             errors = [];
100314         forEach(invalidFields, function(field) {
100315             forEach(field.getActiveErrors(), function(error) {
100316                 var label = field.getFieldLabel();
100317                 errors.push((label ? label + ': ' : '') + error);
100318             });
100319         });
100320         return errors;
100321     },
100322
100323     getTargetEl: function() {
100324         return this.bodyEl || this.callParent();
100325     }
100326 });
100327
100328 /**
100329  * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
100330  * {@link Ext.form.field.Checkbox} controls into columns, and provides convenience
100331  * {@link Ext.form.field.Field} methods for {@link #getValue getting}, {@link #setValue setting},
100332  * and {@link #validate validating} the group of checkboxes as a whole.
100333  *
100334  * # Validation
100335  *
100336  * Individual checkbox fields themselves have no default validation behavior, but
100337  * sometimes you want to require a user to select at least one of a group of checkboxes. CheckboxGroup
100338  * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
100339  * least one of the checkboxes, the entire group will be highlighted as invalid and the
100340  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.
100341  *
100342  * # Layout
100343  *
100344  * The default layout for CheckboxGroup makes it easy to arrange the checkboxes into
100345  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
100346  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
100347  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
100348  * the checkbox components at any depth will still be managed by the CheckboxGroup's validation.
100349  *
100350  *     @example
100351  *     Ext.create('Ext.form.Panel', {
100352  *         title: 'Checkbox Group',
100353  *         width: 300,
100354  *         height: 125,
100355  *         bodyPadding: 10,
100356  *         renderTo: Ext.getBody(),
100357  *         items:[{
100358  *             xtype: 'checkboxgroup',
100359  *             fieldLabel: 'Two Columns',
100360  *             // Arrange radio buttons into two columns, distributed vertically
100361  *             columns: 2,
100362  *             vertical: true,
100363  *             items: [
100364  *                 { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
100365  *                 { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true },
100366  *                 { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
100367  *                 { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
100368  *                 { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
100369  *                 { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
100370  *             ]
100371  *         }]
100372  *     });
100373  */
100374 Ext.define('Ext.form.CheckboxGroup', {
100375     extend:'Ext.form.FieldContainer',
100376     mixins: {
100377         field: 'Ext.form.field.Field'
100378     },
100379     alias: 'widget.checkboxgroup',
100380     requires: ['Ext.layout.container.CheckboxGroup', 'Ext.form.field.Base'],
100381
100382     /**
100383      * @cfg {String} name
100384      * @hide
100385      */
100386
100387     /**
100388      * @cfg {Ext.form.field.Checkbox[]/Object[]} items
100389      * An Array of {@link Ext.form.field.Checkbox Checkbox}es or Checkbox config objects to arrange in the group.
100390      */
100391
100392     /**
100393      * @cfg {String/Number/Number[]} columns
100394      * Specifies the number of columns to use when displaying grouped checkbox/radio controls using automatic layout.
100395      * This config can take several types of values:
100396      *
100397      * - 'auto' - The controls will be rendered one per column on one row and the width of each column will be evenly
100398      *   distributed based on the width of the overall field container. This is the default.
100399      * - Number - If you specific a number (e.g., 3) that number of columns will be created and the contained controls
100400      *   will be automatically distributed based on the value of {@link #vertical}.
100401      * - Array - You can also specify an array of column widths, mixing integer (fixed width) and float (percentage
100402      *   width) values as needed (e.g., [100, .25, .75]). Any integer values will be rendered first, then any float
100403      *   values will be calculated as a percentage of the remaining space. Float values do not have to add up to 1
100404      *   (100%) although if you want the controls to take up the entire field container you should do so.
100405      */
100406     columns : 'auto',
100407
100408     /**
100409      * @cfg {Boolean} vertical
100410      * True to distribute contained controls across columns, completely filling each column top to bottom before
100411      * starting on the next column. The number of controls in each column will be automatically calculated to keep
100412      * columns as even as possible. The default value is false, so that controls will be added to columns one at a time,
100413      * completely filling each row left to right before starting on the next row.
100414      */
100415     vertical : false,
100416
100417     /**
100418      * @cfg {Boolean} allowBlank
100419      * False to validate that at least one item in the group is checked. If no items are selected at
100420      * validation time, {@link #blankText} will be used as the error text.
100421      */
100422     allowBlank : true,
100423
100424     /**
100425      * @cfg {String} blankText
100426      * Error text to display if the {@link #allowBlank} validation fails
100427      */
100428     blankText : "You must select at least one item in this group",
100429
100430     // private
100431     defaultType : 'checkboxfield',
100432
100433     // private
100434     groupCls : Ext.baseCSSPrefix + 'form-check-group',
100435
100436     /**
100437      * @cfg {String} fieldBodyCls
100438      * An extra CSS class to be applied to the body content element in addition to {@link #baseBodyCls}.
100439      * Defaults to 'x-form-checkboxgroup-body'.
100440      */
100441     fieldBodyCls: Ext.baseCSSPrefix + 'form-checkboxgroup-body',
100442
100443     // private
100444     layout: 'checkboxgroup',
100445
100446     initComponent: function() {
100447         var me = this;
100448         me.callParent();
100449         me.initField();
100450     },
100451
100452     /**
100453      * Initializes the field's value based on the initial config. If the {@link #value} config is specified then we use
100454      * that to set the value; otherwise we initialize the originalValue by querying the values of all sub-checkboxes
100455      * after they have been initialized.
100456      * @protected
100457      */
100458     initValue: function() {
100459         var me = this,
100460             valueCfg = me.value;
100461         me.originalValue = me.lastValue = valueCfg || me.getValue();
100462         if (valueCfg) {
100463             me.setValue(valueCfg);
100464         }
100465     },
100466
100467     /**
100468      * When a checkbox is added to the group, monitor it for changes
100469      * @param {Object} field
100470      * @protected
100471      */
100472     onFieldAdded: function(field) {
100473         var me = this;
100474         if (field.isCheckbox) {
100475             me.mon(field, 'change', me.checkChange, me);
100476         }
100477         me.callParent(arguments);
100478     },
100479
100480     onFieldRemoved: function(field) {
100481         var me = this;
100482         if (field.isCheckbox) {
100483             me.mun(field, 'change', me.checkChange, me);
100484         }
100485         me.callParent(arguments);
100486     },
100487
100488     // private override - the group value is a complex object, compare using object serialization
100489     isEqual: function(value1, value2) {
100490         var toQueryString = Ext.Object.toQueryString;
100491         return toQueryString(value1) === toQueryString(value2);
100492     },
100493
100494     /**
100495      * Runs CheckboxGroup's validations and returns an array of any errors. The only error by default is if allowBlank
100496      * is set to true and no items are checked.
100497      * @return {String[]} Array of all validation errors
100498      */
100499     getErrors: function() {
100500         var errors = [];
100501         if (!this.allowBlank && Ext.isEmpty(this.getChecked())) {
100502             errors.push(this.blankText);
100503         }
100504         return errors;
100505     },
100506
100507     /**
100508      * @private Returns all checkbox components within the container
100509      */
100510     getBoxes: function() {
100511         return this.query('[isCheckbox]');
100512     },
100513
100514     /**
100515      * @private Convenience function which calls the given function for every checkbox in the group
100516      * @param {Function} fn The function to call
100517      * @param {Object} scope (Optional) scope object
100518      */
100519     eachBox: function(fn, scope) {
100520         Ext.Array.forEach(this.getBoxes(), fn, scope || this);
100521     },
100522
100523     /**
100524      * Returns an Array of all checkboxes in the container which are currently checked
100525      * @return {Ext.form.field.Checkbox[]} Array of Ext.form.field.Checkbox components
100526      */
100527     getChecked: function() {
100528         return Ext.Array.filter(this.getBoxes(), function(cb) {
100529             return cb.getValue();
100530         });
100531     },
100532
100533     // private override
100534     isDirty: function(){
100535         return Ext.Array.some(this.getBoxes(), function(cb) {
100536             return cb.isDirty();
100537         });
100538     },
100539
100540     // private override
100541     setReadOnly: function(readOnly) {
100542         this.eachBox(function(cb) {
100543             cb.setReadOnly(readOnly);
100544         });
100545         this.readOnly = readOnly;
100546     },
100547
100548     /**
100549      * Resets the checked state of all {@link Ext.form.field.Checkbox checkboxes} in the group to their originally
100550      * loaded values and clears any validation messages.
100551      * See {@link Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
100552      */
100553     reset: function() {
100554         var me = this,
100555             hadError = me.hasActiveError(),
100556             preventMark = me.preventMark;
100557         me.preventMark = true;
100558         me.batchChanges(function() {
100559             me.eachBox(function(cb) {
100560                 cb.reset();
100561             });
100562         });
100563         me.preventMark = preventMark;
100564         me.unsetActiveError();
100565         if (hadError) {
100566             me.doComponentLayout();
100567         }
100568     },
100569
100570     // private override
100571     resetOriginalValue: function() {
100572         // Defer resetting of originalValue until after all sub-checkboxes have been reset so we get
100573         // the correct data from getValue()
100574         Ext.defer(function() {
100575             this.callParent();
100576         }, 1, this);
100577     },
100578
100579
100580     /**
100581      * Sets the value(s) of all checkboxes in the group. The expected format is an Object of name-value pairs
100582      * corresponding to the names of the checkboxes in the group. Each pair can have either a single or multiple values:
100583      *
100584      *   - A single Boolean or String value will be passed to the `setValue` method of the checkbox with that name.
100585      *     See the rules in {@link Ext.form.field.Checkbox#setValue} for accepted values.
100586      *   - An Array of String values will be matched against the {@link Ext.form.field.Checkbox#inputValue inputValue}
100587      *     of checkboxes in the group with that name; those checkboxes whose inputValue exists in the array will be
100588      *     checked and others will be unchecked.
100589      *
100590      * If a checkbox's name is not in the mapping at all, it will be unchecked.
100591      *
100592      * An example:
100593      *
100594      *     var myCheckboxGroup = new Ext.form.CheckboxGroup({
100595      *         columns: 3,
100596      *         items: [{
100597      *             name: 'cb1',
100598      *             boxLabel: 'Single 1'
100599      *         }, {
100600      *             name: 'cb2',
100601      *             boxLabel: 'Single 2'
100602      *         }, {
100603      *             name: 'cb3',
100604      *             boxLabel: 'Single 3'
100605      *         }, {
100606      *             name: 'cbGroup',
100607      *             boxLabel: 'Grouped 1'
100608      *             inputValue: 'value1'
100609      *         }, {
100610      *             name: 'cbGroup',
100611      *             boxLabel: 'Grouped 2'
100612      *             inputValue: 'value2'
100613      *         }, {
100614      *             name: 'cbGroup',
100615      *             boxLabel: 'Grouped 3'
100616      *             inputValue: 'value3'
100617      *         }]
100618      *     });
100619      *
100620      *     myCheckboxGroup.setValue({
100621      *         cb1: true,
100622      *         cb3: false,
100623      *         cbGroup: ['value1', 'value3']
100624      *     });
100625      *
100626      * The above code will cause the checkbox named 'cb1' to be checked, as well as the first and third checkboxes named
100627      * 'cbGroup'. The other three checkboxes will be unchecked.
100628      *
100629      * @param {Object} value The mapping of checkbox names to values.
100630      * @return {Ext.form.CheckboxGroup} this
100631      */
100632     setValue: function(value) {
100633         var me = this;
100634         me.batchChanges(function() {
100635             me.eachBox(function(cb) {
100636                 var name = cb.getName(),
100637                     cbValue = false;
100638                 if (value && name in value) {
100639                     if (Ext.isArray(value[name])) {
100640                         cbValue = Ext.Array.contains(value[name], cb.inputValue);
100641                     } else {
100642                         // single value, let the checkbox's own setValue handle conversion
100643                         cbValue = value[name];
100644                     }
100645                 }
100646                 cb.setValue(cbValue);
100647             });
100648         });
100649         return me;
100650     },
100651
100652
100653     /**
100654      * Returns an object containing the values of all checked checkboxes within the group. Each key-value pair in the
100655      * object corresponds to a checkbox {@link Ext.form.field.Checkbox#name name}. If there is only one checked checkbox
100656      * with a particular name, the value of that pair will be the String {@link Ext.form.field.Checkbox#inputValue
100657      * inputValue} of that checkbox. If there are multiple checked checkboxes with that name, the value of that pair
100658      * will be an Array of the selected inputValues.
100659      *
100660      * The object format returned from this method can also be passed directly to the {@link #setValue} method.
100661      *
100662      * NOTE: In Ext 3, this method returned an array of Checkbox components; this was changed to make it more consistent
100663      * with other field components and with the {@link #setValue} argument signature. If you need the old behavior in
100664      * Ext 4+, use the {@link #getChecked} method instead.
100665      */
100666     getValue: function() {
100667         var values = {};
100668         this.eachBox(function(cb) {
100669             var name = cb.getName(),
100670                 inputValue = cb.inputValue,
100671                 bucket;
100672             if (cb.getValue()) {
100673                 if (name in values) {
100674                     bucket = values[name];
100675                     if (!Ext.isArray(bucket)) {
100676                         bucket = values[name] = [bucket];
100677                     }
100678                     bucket.push(inputValue);
100679                 } else {
100680                     values[name] = inputValue;
100681                 }
100682             }
100683         });
100684         return values;
100685     },
100686
100687     /*
100688      * Don't return any data for submit; the form will get the info from the individual checkboxes themselves.
100689      */
100690     getSubmitData: function() {
100691         return null;
100692     },
100693
100694     /*
100695      * Don't return any data for the model; the form will get the info from the individual checkboxes themselves.
100696      */
100697     getModelData: function() {
100698         return null;
100699     },
100700
100701     validate: function() {
100702         var me = this,
100703             errors = me.getErrors(),
100704             isValid = Ext.isEmpty(errors),
100705             wasValid = !me.hasActiveError();
100706
100707         if (isValid) {
100708             me.unsetActiveError();
100709         } else {
100710             me.setActiveError(errors);
100711         }
100712         if (isValid !== wasValid) {
100713             me.fireEvent('validitychange', me, isValid);
100714             me.doComponentLayout();
100715         }
100716
100717         return isValid;
100718     }
100719
100720 }, function() {
100721
100722     this.borrow(Ext.form.field.Base, ['markInvalid', 'clearInvalid']);
100723
100724 });
100725
100726
100727 /**
100728  * @private
100729  * Private utility class for managing all {@link Ext.form.field.Checkbox} fields grouped by name.
100730  */
100731 Ext.define('Ext.form.CheckboxManager', {
100732     extend: 'Ext.util.MixedCollection',
100733     singleton: true,
100734
100735     getByName: function(name) {
100736         return this.filterBy(function(item) {
100737             return item.name == name;
100738         });
100739     },
100740
100741     getWithValue: function(name, value) {
100742         return this.filterBy(function(item) {
100743             return item.name == name && item.inputValue == value;
100744         });
100745     },
100746
100747     getChecked: function(name) {
100748         return this.filterBy(function(item) {
100749             return item.name == name && item.checked;
100750         });
100751     }
100752 });
100753
100754 /**
100755  * @docauthor Jason Johnston <jason@sencha.com>
100756  *
100757  * A container for grouping sets of fields, rendered as a HTML `fieldset` element. The {@link #title}
100758  * config will be rendered as the fieldset's `legend`.
100759  *
100760  * While FieldSets commonly contain simple groups of fields, they are general {@link Ext.container.Container Containers}
100761  * and may therefore contain any type of components in their {@link #items}, including other nested containers.
100762  * The default {@link #layout} for the FieldSet's items is `'anchor'`, but it can be configured to use any other
100763  * layout type.
100764  *
100765  * FieldSets may also be collapsed if configured to do so; this can be done in two ways:
100766  *
100767  * 1. Set the {@link #collapsible} config to true; this will result in a collapse button being rendered next to
100768  *    the {@link #title legend title}, or:
100769  * 2. Set the {@link #checkboxToggle} config to true; this is similar to using {@link #collapsible} but renders
100770  *    a {@link Ext.form.field.Checkbox checkbox} in place of the toggle button. The fieldset will be expanded when the
100771  *    checkbox is checked and collapsed when it is unchecked. The checkbox will also be included in the
100772  *    {@link Ext.form.Basic#submit form submit parameters} using the {@link #checkboxName} as its parameter name.
100773  *
100774  * # Example usage
100775  *
100776  *     @example
100777  *     Ext.create('Ext.form.Panel', {
100778  *         title: 'Simple Form with FieldSets',
100779  *         labelWidth: 75, // label settings here cascade unless overridden
100780  *         url: 'save-form.php',
100781  *         frame: true,
100782  *         bodyStyle: 'padding:5px 5px 0',
100783  *         width: 550,
100784  *         renderTo: Ext.getBody(),
100785  *         layout: 'column', // arrange fieldsets side by side
100786  *         defaults: {
100787  *             bodyPadding: 4
100788  *         },
100789  *         items: [{
100790  *             // Fieldset in Column 1 - collapsible via toggle button
100791  *             xtype:'fieldset',
100792  *             columnWidth: 0.5,
100793  *             title: 'Fieldset 1',
100794  *             collapsible: true,
100795  *             defaultType: 'textfield',
100796  *             defaults: {anchor: '100%'},
100797  *             layout: 'anchor',
100798  *             items :[{
100799  *                 fieldLabel: 'Field 1',
100800  *                 name: 'field1'
100801  *             }, {
100802  *                 fieldLabel: 'Field 2',
100803  *                 name: 'field2'
100804  *             }]
100805  *         }, {
100806  *             // Fieldset in Column 2 - collapsible via checkbox, collapsed by default, contains a panel
100807  *             xtype:'fieldset',
100808  *             title: 'Show Panel', // title or checkboxToggle creates fieldset header
100809  *             columnWidth: 0.5,
100810  *             checkboxToggle: true,
100811  *             collapsed: true, // fieldset initially collapsed
100812  *             layout:'anchor',
100813  *             items :[{
100814  *                 xtype: 'panel',
100815  *                 anchor: '100%',
100816  *                 title: 'Panel inside a fieldset',
100817  *                 frame: true,
100818  *                 height: 52
100819  *             }]
100820  *         }]
100821  *     });
100822  */
100823 Ext.define('Ext.form.FieldSet', {
100824     extend: 'Ext.container.Container',
100825     alias: 'widget.fieldset',
100826     uses: ['Ext.form.field.Checkbox', 'Ext.panel.Tool', 'Ext.layout.container.Anchor', 'Ext.layout.component.FieldSet'],
100827
100828     /**
100829      * @cfg {String} title
100830      * A title to be displayed in the fieldset's legend. May contain HTML markup.
100831      */
100832
100833     /**
100834      * @cfg {Boolean} [checkboxToggle=false]
100835      * Set to true to render a checkbox into the fieldset frame just in front of the legend to expand/collapse the
100836      * fieldset when the checkbox is toggled.. This checkbox will be included in form submits using
100837      * the {@link #checkboxName}.
100838      */
100839
100840     /**
100841      * @cfg {String} checkboxName
100842      * The name to assign to the fieldset's checkbox if {@link #checkboxToggle} = true
100843      * (defaults to '[fieldset id]-checkbox').
100844      */
100845
100846     /**
100847      * @cfg {Boolean} [collapsible=false]
100848      * Set to true to make the fieldset collapsible and have the expand/collapse toggle button automatically rendered
100849      * into the legend element, false to keep the fieldset statically sized with no collapse button.
100850      * Another option is to configure {@link #checkboxToggle}. Use the {@link #collapsed} config to collapse the
100851      * fieldset by default.
100852      */
100853
100854     /**
100855      * @cfg {Boolean} collapsed
100856      * Set to true to render the fieldset as collapsed by default. If {@link #checkboxToggle} is specified, the checkbox
100857      * will also be unchecked by default.
100858      */
100859     collapsed: false,
100860
100861     /**
100862      * @property {Ext.Component} legend
100863      * The component for the fieldset's legend. Will only be defined if the configuration requires a legend to be
100864      * created, by setting the {@link #title} or {@link #checkboxToggle} options.
100865      */
100866
100867     /**
100868      * @cfg {String} [baseCls='x-fieldset']
100869      * The base CSS class applied to the fieldset.
100870      */
100871     baseCls: Ext.baseCSSPrefix + 'fieldset',
100872
100873     /**
100874      * @cfg {String} layout
100875      * The {@link Ext.container.Container#layout} for the fieldset's immediate child items.
100876      */
100877     layout: 'anchor',
100878
100879     componentLayout: 'fieldset',
100880
100881     // No aria role necessary as fieldset has its own recognized semantics
100882     ariaRole: '',
100883
100884     renderTpl: ['<div id="{id}-body" class="{baseCls}-body"></div>'],
100885
100886     maskOnDisable: false,
100887
100888     getElConfig: function(){
100889         return {tag: 'fieldset', id: this.id};
100890     },
100891
100892     initComponent: function() {
100893         var me = this,
100894             baseCls = me.baseCls;
100895
100896         me.callParent();
100897
100898         // Create the Legend component if needed
100899         me.initLegend();
100900
100901         // Add body el
100902         me.addChildEls('body');
100903
100904         if (me.collapsed) {
100905             me.addCls(baseCls + '-collapsed');
100906             me.collapse();
100907         }
100908     },
100909
100910     // private
100911     onRender: function(container, position) {
100912         this.callParent(arguments);
100913         // Make sure the legend is created and rendered
100914         this.initLegend();
100915     },
100916
100917     /**
100918      * @private
100919      * Initialize and render the legend component if necessary
100920      */
100921     initLegend: function() {
100922         var me = this,
100923             legendItems,
100924             legend = me.legend;
100925
100926         // Create the legend component if needed and it hasn't been already
100927         if (!legend && (me.title || me.checkboxToggle || me.collapsible)) {
100928             legendItems = [];
100929
100930             // Checkbox
100931             if (me.checkboxToggle) {
100932                 legendItems.push(me.createCheckboxCmp());
100933             }
100934             // Toggle button
100935             else if (me.collapsible) {
100936                 legendItems.push(me.createToggleCmp());
100937             }
100938
100939             // Title
100940             legendItems.push(me.createTitleCmp());
100941
100942             legend = me.legend = Ext.create('Ext.container.Container', {
100943                 baseCls: me.baseCls + '-header',
100944                 ariaRole: '',
100945                 ownerCt: this,
100946                 getElConfig: function(){
100947                     var result = {
100948                         tag: 'legend',
100949                         cls: this.baseCls
100950                     };
100951
100952                     // Gecko3 will kick every <div> out of <legend> and mess up every thing.
100953                     // So here we change every <div> into <span>s. Therefore the following
100954                     // clearer is not needed and since div introduces a lot of subsequent
100955                     // problems, it is actually harmful.
100956                     if (!Ext.isGecko3) {
100957                         result.children = [{
100958                             cls: Ext.baseCSSPrefix + 'clear'
100959                         }];
100960                     }
100961                     return result;
100962                 },
100963                 items: legendItems
100964             });
100965         }
100966
100967         // Make sure legend is rendered if the fieldset is rendered
100968         if (legend && !legend.rendered && me.rendered) {
100969             me.legend.render(me.el, me.body); //insert before body element
100970         }
100971     },
100972
100973     /**
100974      * Creates the legend title component. This is only called internally, but could be overridden in subclasses to
100975      * customize the title component.
100976      * @return Ext.Component
100977      * @protected
100978      */
100979     createTitleCmp: function() {
100980         var me = this;
100981         me.titleCmp = Ext.create('Ext.Component', {
100982             html: me.title,
100983             getElConfig: function() {
100984                 return {
100985                     tag: Ext.isGecko3 ? 'span' : 'div',
100986                     cls: me.titleCmp.cls,
100987                     id: me.titleCmp.id
100988                 };
100989             },
100990             cls: me.baseCls + '-header-text'
100991         });
100992         return me.titleCmp;
100993     },
100994
100995     /**
100996      * @property {Ext.form.field.Checkbox} checkboxCmp
100997      * Refers to the {@link Ext.form.field.Checkbox} component that is added next to the title in the legend. Only
100998      * populated if the fieldset is configured with {@link #checkboxToggle}:true.
100999      */
101000
101001     /**
101002      * Creates the checkbox component. This is only called internally, but could be overridden in subclasses to
101003      * customize the checkbox's configuration or even return an entirely different component type.
101004      * @return Ext.Component
101005      * @protected
101006      */
101007     createCheckboxCmp: function() {
101008         var me = this,
101009             suffix = '-checkbox';
101010
101011         me.checkboxCmp = Ext.create('Ext.form.field.Checkbox', {
101012             getElConfig: function() {
101013                 return {
101014                     tag: Ext.isGecko3 ? 'span' : 'div',
101015                     id: me.checkboxCmp.id,
101016                     cls: me.checkboxCmp.cls
101017                 };
101018             },
101019             name: me.checkboxName || me.id + suffix,
101020             cls: me.baseCls + '-header' + suffix,
101021             checked: !me.collapsed,
101022             listeners: {
101023                 change: me.onCheckChange,
101024                 scope: me
101025             }
101026         });
101027         return me.checkboxCmp;
101028     },
101029
101030     /**
101031      * @property {Ext.panel.Tool} toggleCmp
101032      * Refers to the {@link Ext.panel.Tool} component that is added as the collapse/expand button next to the title in
101033      * the legend. Only populated if the fieldset is configured with {@link #collapsible}:true.
101034      */
101035
101036     /**
101037      * Creates the toggle button component. This is only called internally, but could be overridden in subclasses to
101038      * customize the toggle component.
101039      * @return Ext.Component
101040      * @protected
101041      */
101042     createToggleCmp: function() {
101043         var me = this;
101044         me.toggleCmp = Ext.create('Ext.panel.Tool', {
101045             getElConfig: function() {
101046                 return {
101047                     tag: Ext.isGecko3 ? 'span' : 'div',
101048                     id: me.toggleCmp.id,
101049                     cls: me.toggleCmp.cls
101050                 };
101051             },
101052             type: 'toggle',
101053             handler: me.toggle,
101054             scope: me
101055         });
101056         return me.toggleCmp;
101057     },
101058
101059     /**
101060      * Sets the title of this fieldset
101061      * @param {String} title The new title
101062      * @return {Ext.form.FieldSet} this
101063      */
101064     setTitle: function(title) {
101065         var me = this;
101066         me.title = title;
101067         me.initLegend();
101068         me.titleCmp.update(title);
101069         return me;
101070     },
101071
101072     getTargetEl : function() {
101073         return this.body || this.frameBody || this.el;
101074     },
101075
101076     getContentTarget: function() {
101077         return this.body;
101078     },
101079
101080     /**
101081      * @private
101082      * Include the legend component in the items for ComponentQuery
101083      */
101084     getRefItems: function(deep) {
101085         var refItems = this.callParent(arguments),
101086             legend = this.legend;
101087
101088         // Prepend legend items to ensure correct order
101089         if (legend) {
101090             refItems.unshift(legend);
101091             if (deep) {
101092                 refItems.unshift.apply(refItems, legend.getRefItems(true));
101093             }
101094         }
101095         return refItems;
101096     },
101097
101098     /**
101099      * Expands the fieldset.
101100      * @return {Ext.form.FieldSet} this
101101      */
101102     expand : function(){
101103         return this.setExpanded(true);
101104     },
101105
101106     /**
101107      * Collapses the fieldset.
101108      * @return {Ext.form.FieldSet} this
101109      */
101110     collapse : function() {
101111         return this.setExpanded(false);
101112     },
101113
101114     /**
101115      * @private Collapse or expand the fieldset
101116      */
101117     setExpanded: function(expanded) {
101118         var me = this,
101119             checkboxCmp = me.checkboxCmp;
101120
101121         expanded = !!expanded;
101122
101123         if (checkboxCmp) {
101124             checkboxCmp.setValue(expanded);
101125         }
101126
101127         if (expanded) {
101128             me.removeCls(me.baseCls + '-collapsed');
101129         } else {
101130             me.addCls(me.baseCls + '-collapsed');
101131         }
101132         me.collapsed = !expanded;
101133         if (expanded) {
101134             // ensure subitems will get rendered and layed out when expanding
101135             me.getComponentLayout().childrenChanged = true;
101136         }
101137         me.doComponentLayout();
101138         return me;
101139     },
101140
101141     /**
101142      * Toggle the fieldset's collapsed state to the opposite of what it is currently
101143      */
101144     toggle: function() {
101145         this.setExpanded(!!this.collapsed);
101146     },
101147
101148     /**
101149      * @private
101150      * Handle changes in the checkbox checked state
101151      */
101152     onCheckChange: function(cmp, checked) {
101153         this.setExpanded(checked);
101154     },
101155
101156     beforeDestroy : function() {
101157         var legend = this.legend;
101158         if (legend) {
101159             legend.destroy();
101160         }
101161         this.callParent();
101162     }
101163 });
101164
101165 /**
101166  * @docauthor Jason Johnston <jason@sencha.com>
101167  *
101168  * Produces a standalone `<label />` element which can be inserted into a form and be associated with a field
101169  * in that form using the {@link #forId} property.
101170  * 
101171  * **NOTE:** in most cases it will be more appropriate to use the {@link Ext.form.Labelable#fieldLabel fieldLabel}
101172  * and associated config properties ({@link Ext.form.Labelable#labelAlign}, {@link Ext.form.Labelable#labelWidth},
101173  * etc.) in field components themselves, as that allows labels to be uniformly sized throughout the form.
101174  * Ext.form.Label should only be used when your layout can not be achieved with the standard
101175  * {@link Ext.form.Labelable field layout}.
101176  * 
101177  * You will likely be associating the label with a field component that extends {@link Ext.form.field.Base}, so
101178  * you should make sure the {@link #forId} is set to the same value as the {@link Ext.form.field.Base#inputId inputId}
101179  * of that field.
101180  * 
101181  * The label's text can be set using either the {@link #text} or {@link #html} configuration properties; the
101182  * difference between the two is that the former will automatically escape HTML characters when rendering, while
101183  * the latter will not.
101184  *
101185  * # Example
101186  * 
101187  * This example creates a Label after its associated Text field, an arrangement that cannot currently
101188  * be achieved using the standard Field layout's labelAlign.
101189  * 
101190  *     @example
101191  *     Ext.create('Ext.form.Panel', {
101192  *         title: 'Field with Label',
101193  *         width: 400,
101194  *         bodyPadding: 10,
101195  *         renderTo: Ext.getBody(),
101196  *         layout: {
101197  *             type: 'hbox',
101198  *             align: 'middle'
101199  *         },
101200  *         items: [{
101201  *             xtype: 'textfield',
101202  *             hideLabel: true,
101203  *             flex: 1
101204  *         }, {
101205  *             xtype: 'label',
101206  *             forId: 'myFieldId',
101207  *             text: 'My Awesome Field',
101208  *             margins: '0 0 0 10'
101209  *         }]
101210  *     });
101211  */
101212 Ext.define('Ext.form.Label', {
101213     extend:'Ext.Component',
101214     alias: 'widget.label',
101215     requires: ['Ext.util.Format'],
101216
101217     /**
101218      * @cfg {String} [text='']
101219      * The plain text to display within the label. If you need to include HTML
101220      * tags within the label's innerHTML, use the {@link #html} config instead.
101221      */
101222     /**
101223      * @cfg {String} forId
101224      * The id of the input element to which this label will be bound via the standard HTML 'for'
101225      * attribute. If not specified, the attribute will not be added to the label. In most cases you will be
101226      * associating the label with a {@link Ext.form.field.Base} component, so you should make sure this matches
101227      * the {@link Ext.form.field.Base#inputId inputId} of that field.
101228      */
101229     /**
101230      * @cfg {String} [html='']
101231      * An HTML fragment that will be used as the label's innerHTML.
101232      * Note that if {@link #text} is specified it will take precedence and this value will be ignored.
101233      */
101234     
101235     maskOnDisable: false,
101236     getElConfig: function(){
101237         var me = this;
101238         return {
101239             tag: 'label', 
101240             id: me.id, 
101241             htmlFor: me.forId || '',
101242             html: me.text ? Ext.util.Format.htmlEncode(me.text) : (me.html || '') 
101243         };
101244     },
101245
101246     /**
101247      * Updates the label's innerHTML with the specified string.
101248      * @param {String} text The new label text
101249      * @param {Boolean} [encode=true] False to skip HTML-encoding the text when rendering it
101250      * to the label. This might be useful if you want to include tags in the label's innerHTML rather
101251      * than rendering them as string literals per the default logic.
101252      * @return {Ext.form.Label} this
101253      */
101254     setText : function(text, encode){
101255         var me = this;
101256         
101257         encode = encode !== false;
101258         if(encode) {
101259             me.text = text;
101260             delete me.html;
101261         } else {
101262             me.html = text;
101263             delete me.text;
101264         }
101265         
101266         if(me.rendered){
101267             me.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(text) : text;
101268         }
101269         return this;
101270     }
101271 });
101272
101273
101274 /**
101275  * @docauthor Jason Johnston <jason@sencha.com>
101276  * 
101277  * FormPanel provides a standard container for forms. It is essentially a standard {@link Ext.panel.Panel} which
101278  * automatically creates a {@link Ext.form.Basic BasicForm} for managing any {@link Ext.form.field.Field}
101279  * objects that are added as descendants of the panel. It also includes conveniences for configuring and
101280  * working with the BasicForm and the collection of Fields.
101281  * 
101282  * # Layout
101283  * 
101284  * By default, FormPanel is configured with `{@link Ext.layout.container.Anchor layout:'anchor'}` for
101285  * the layout of its immediate child items. This can be changed to any of the supported container layouts.
101286  * The layout of sub-containers is configured in {@link Ext.container.Container#layout the standard way}.
101287  * 
101288  * # BasicForm
101289  * 
101290  * Although **not listed** as configuration options of FormPanel, the FormPanel class accepts all
101291  * of the config options supported by the {@link Ext.form.Basic} class, and will pass them along to
101292  * the internal BasicForm when it is created.
101293  * 
101294  * **Note**: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
101295  * the `initialConfig` property of the FormPanel. Applying {@link Ext.form.Basic BasicForm}
101296  * configuration settings to `this` will *not* affect the BasicForm's configuration.
101297  * 
101298  * The following events fired by the BasicForm will be re-fired by the FormPanel and can therefore be
101299  * listened for on the FormPanel itself:
101300  * 
101301  * - {@link Ext.form.Basic#beforeaction beforeaction}
101302  * - {@link Ext.form.Basic#actionfailed actionfailed}
101303  * - {@link Ext.form.Basic#actioncomplete actioncomplete}
101304  * - {@link Ext.form.Basic#validitychange validitychange}
101305  * - {@link Ext.form.Basic#dirtychange dirtychange}
101306  * 
101307  * # Field Defaults
101308  * 
101309  * The {@link #fieldDefaults} config option conveniently allows centralized configuration of default values
101310  * for all fields added as descendants of the FormPanel. Any config option recognized by implementations
101311  * of {@link Ext.form.Labelable} may be included in this object. See the {@link #fieldDefaults} documentation
101312  * for details of how the defaults are applied.
101313  * 
101314  * # Form Validation
101315  * 
101316  * With the default configuration, form fields are validated on-the-fly while the user edits their values.
101317  * This can be controlled on a per-field basis (or via the {@link #fieldDefaults} config) with the field
101318  * config properties {@link Ext.form.field.Field#validateOnChange} and {@link Ext.form.field.Base#checkChangeEvents},
101319  * and the FormPanel's config properties {@link #pollForChanges} and {@link #pollInterval}.
101320  * 
101321  * Any component within the FormPanel can be configured with `formBind: true`. This will cause that
101322  * component to be automatically disabled when the form is invalid, and enabled when it is valid. This is most
101323  * commonly used for Button components to prevent submitting the form in an invalid state, but can be used on
101324  * any component type.
101325  * 
101326  * For more information on form validation see the following:
101327  * 
101328  * - {@link Ext.form.field.Field#validateOnChange}
101329  * - {@link #pollForChanges} and {@link #pollInterval}
101330  * - {@link Ext.form.field.VTypes}
101331  * - {@link Ext.form.Basic#doAction BasicForm.doAction clientValidation notes}
101332  * 
101333  * # Form Submission
101334  * 
101335  * By default, Ext Forms are submitted through Ajax, using {@link Ext.form.action.Action}. See the documentation for
101336  * {@link Ext.form.Basic} for details.
101337  *
101338  * # Example usage
101339  * 
101340  *     @example
101341  *     Ext.create('Ext.form.Panel', {
101342  *         title: 'Simple Form',
101343  *         bodyPadding: 5,
101344  *         width: 350,
101345  * 
101346  *         // The form will submit an AJAX request to this URL when submitted
101347  *         url: 'save-form.php',
101348  * 
101349  *         // Fields will be arranged vertically, stretched to full width
101350  *         layout: 'anchor',
101351  *         defaults: {
101352  *             anchor: '100%'
101353  *         },
101354  * 
101355  *         // The fields
101356  *         defaultType: 'textfield',
101357  *         items: [{
101358  *             fieldLabel: 'First Name',
101359  *             name: 'first',
101360  *             allowBlank: false
101361  *         },{
101362  *             fieldLabel: 'Last Name',
101363  *             name: 'last',
101364  *             allowBlank: false
101365  *         }],
101366  * 
101367  *         // Reset and Submit buttons
101368  *         buttons: [{
101369  *             text: 'Reset',
101370  *             handler: function() {
101371  *                 this.up('form').getForm().reset();
101372  *             }
101373  *         }, {
101374  *             text: 'Submit',
101375  *             formBind: true, //only enabled once the form is valid
101376  *             disabled: true,
101377  *             handler: function() {
101378  *                 var form = this.up('form').getForm();
101379  *                 if (form.isValid()) {
101380  *                     form.submit({
101381  *                         success: function(form, action) {
101382  *                            Ext.Msg.alert('Success', action.result.msg);
101383  *                         },
101384  *                         failure: function(form, action) {
101385  *                             Ext.Msg.alert('Failed', action.result.msg);
101386  *                         }
101387  *                     });
101388  *                 }
101389  *             }
101390  *         }],
101391  *         renderTo: Ext.getBody()
101392  *     });
101393  *
101394  */
101395 Ext.define('Ext.form.Panel', {
101396     extend:'Ext.panel.Panel',
101397     mixins: {
101398         fieldAncestor: 'Ext.form.FieldAncestor'
101399     },
101400     alias: 'widget.form',
101401     alternateClassName: ['Ext.FormPanel', 'Ext.form.FormPanel'],
101402     requires: ['Ext.form.Basic', 'Ext.util.TaskRunner'],
101403
101404     /**
101405      * @cfg {Boolean} pollForChanges
101406      * If set to `true`, sets up an interval task (using the {@link #pollInterval}) in which the
101407      * panel's fields are repeatedly checked for changes in their values. This is in addition to the normal detection
101408      * each field does on its own input element, and is not needed in most cases. It does, however, provide a
101409      * means to absolutely guarantee detection of all changes including some edge cases in some browsers which
101410      * do not fire native events. Defaults to `false`.
101411      */
101412
101413     /**
101414      * @cfg {Number} pollInterval
101415      * Interval in milliseconds at which the form's fields are checked for value changes. Only used if
101416      * the {@link #pollForChanges} option is set to `true`. Defaults to 500 milliseconds.
101417      */
101418
101419     /**
101420      * @cfg {String} layout
101421      * The {@link Ext.container.Container#layout} for the form panel's immediate child items.
101422      * Defaults to `'anchor'`.
101423      */
101424     layout: 'anchor',
101425
101426     ariaRole: 'form',
101427
101428     initComponent: function() {
101429         var me = this;
101430
101431         if (me.frame) {
101432             me.border = false;
101433         }
101434
101435         me.initFieldAncestor();
101436         me.callParent();
101437
101438         me.relayEvents(me.form, [
101439             'beforeaction',
101440             'actionfailed',
101441             'actioncomplete',
101442             'validitychange',
101443             'dirtychange'
101444         ]);
101445
101446         // Start polling if configured
101447         if (me.pollForChanges) {
101448             me.startPolling(me.pollInterval || 500);
101449         }
101450     },
101451
101452     initItems: function() {
101453         // Create the BasicForm
101454         var me = this;
101455
101456         me.form = me.createForm();
101457         me.callParent();
101458         me.form.initialize();
101459     },
101460
101461     /**
101462      * @private
101463      */
101464     createForm: function() {
101465         return Ext.create('Ext.form.Basic', this, Ext.applyIf({listeners: {}}, this.initialConfig));
101466     },
101467
101468     /**
101469      * Provides access to the {@link Ext.form.Basic Form} which this Panel contains.
101470      * @return {Ext.form.Basic} The {@link Ext.form.Basic Form} which this Panel contains.
101471      */
101472     getForm: function() {
101473         return this.form;
101474     },
101475
101476     /**
101477      * Loads an {@link Ext.data.Model} into this form (internally just calls {@link Ext.form.Basic#loadRecord})
101478      * See also {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}.
101479      * @param {Ext.data.Model} record The record to load
101480      * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel
101481      */
101482     loadRecord: function(record) {
101483         return this.getForm().loadRecord(record);
101484     },
101485
101486     /**
101487      * Returns the currently loaded Ext.data.Model instance if one was loaded via {@link #loadRecord}.
101488      * @return {Ext.data.Model} The loaded instance
101489      */
101490     getRecord: function() {
101491         return this.getForm().getRecord();
101492     },
101493
101494     /**
101495      * Convenience function for fetching the current value of each field in the form. This is the same as calling
101496      * {@link Ext.form.Basic#getValues this.getForm().getValues()}
101497      * @return {Object} The current form field values, keyed by field name
101498      */
101499     getValues: function() {
101500         return this.getForm().getValues();
101501     },
101502
101503     beforeDestroy: function() {
101504         this.stopPolling();
101505         this.form.destroy();
101506         this.callParent();
101507     },
101508
101509     /**
101510      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#load} call.
101511      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#load} and
101512      * {@link Ext.form.Basic#doAction} for details)
101513      */
101514     load: function(options) {
101515         this.form.load(options);
101516     },
101517
101518     /**
101519      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#submit} call.
101520      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#submit} and
101521      * {@link Ext.form.Basic#doAction} for details)
101522      */
101523     submit: function(options) {
101524         this.form.submit(options);
101525     },
101526
101527     /*
101528      * Inherit docs, not using onDisable because it only gets fired
101529      * when the component is rendered.
101530      */
101531     disable: function(silent) {
101532         this.callParent(arguments);
101533         this.form.getFields().each(function(field) {
101534             field.disable();
101535         });
101536     },
101537
101538     /*
101539      * Inherit docs, not using onEnable because it only gets fired
101540      * when the component is rendered.
101541      */
101542     enable: function(silent) {
101543         this.callParent(arguments);
101544         this.form.getFields().each(function(field) {
101545             field.enable();
101546         });
101547     },
101548
101549     /**
101550      * Start an interval task to continuously poll all the fields in the form for changes in their
101551      * values. This is normally started automatically by setting the {@link #pollForChanges} config.
101552      * @param {Number} interval The interval in milliseconds at which the check should run.
101553      */
101554     startPolling: function(interval) {
101555         this.stopPolling();
101556         var task = Ext.create('Ext.util.TaskRunner', interval);
101557         task.start({
101558             interval: 0,
101559             run: this.checkChange,
101560             scope: this
101561         });
101562         this.pollTask = task;
101563     },
101564
101565     /**
101566      * Stop a running interval task that was started by {@link #startPolling}.
101567      */
101568     stopPolling: function() {
101569         var task = this.pollTask;
101570         if (task) {
101571             task.stopAll();
101572             delete this.pollTask;
101573         }
101574     },
101575
101576     /**
101577      * Forces each field within the form panel to
101578      * {@link Ext.form.field.Field#checkChange check if its value has changed}.
101579      */
101580     checkChange: function() {
101581         this.form.getFields().each(function(field) {
101582             field.checkChange();
101583         });
101584     }
101585 });
101586
101587 /**
101588  * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
101589  * {@link Ext.form.field.Radio} controls into columns, and provides convenience {@link Ext.form.field.Field}
101590  * methods for {@link #getValue getting}, {@link #setValue setting}, and {@link #validate validating} the
101591  * group of radio buttons as a whole.
101592  *
101593  * # Validation
101594  *
101595  * Individual radio buttons themselves have no default validation behavior, but
101596  * sometimes you want to require a user to select one of a group of radios. RadioGroup
101597  * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
101598  * one of the radio buttons, the entire group will be highlighted as invalid and the
101599  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.</p>
101600  *
101601  * # Layout
101602  *
101603  * The default layout for RadioGroup makes it easy to arrange the radio buttons into
101604  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
101605  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
101606  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
101607  * the Radio components at any depth will still be managed by the RadioGroup's validation.
101608  *
101609  * # Example usage
101610  *
101611  *     @example
101612  *     Ext.create('Ext.form.Panel', {
101613  *         title: 'RadioGroup Example',
101614  *         width: 300,
101615  *         height: 125,
101616  *         bodyPadding: 10,
101617  *         renderTo: Ext.getBody(),
101618  *         items:[{
101619  *             xtype: 'radiogroup',
101620  *             fieldLabel: 'Two Columns',
101621  *             // Arrange radio buttons into two columns, distributed vertically
101622  *             columns: 2,
101623  *             vertical: true,
101624  *             items: [
101625  *                 { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
101626  *                 { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},
101627  *                 { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
101628  *                 { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
101629  *                 { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
101630  *                 { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
101631  *             ]
101632  *         }]
101633  *     });
101634  *
101635  */
101636 Ext.define('Ext.form.RadioGroup', {
101637     extend: 'Ext.form.CheckboxGroup',
101638     alias: 'widget.radiogroup',
101639
101640     /**
101641      * @cfg {Ext.form.field.Radio[]/Object[]} items
101642      * An Array of {@link Ext.form.field.Radio Radio}s or Radio config objects to arrange in the group.
101643      */
101644     /**
101645      * @cfg {Boolean} allowBlank True to allow every item in the group to be blank.
101646      * If allowBlank = false and no items are selected at validation time, {@link #blankText} will
101647      * be used as the error text.
101648      */
101649     allowBlank : true,
101650     /**
101651      * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
101652      */
101653     blankText : 'You must select one item in this group',
101654
101655     // private
101656     defaultType : 'radiofield',
101657
101658     // private
101659     groupCls : Ext.baseCSSPrefix + 'form-radio-group',
101660
101661     getBoxes: function() {
101662         return this.query('[isRadio]');
101663     },
101664
101665     /**
101666      * Sets the value of the radio group. The radio with corresponding name and value will be set.
101667      * This method is simpler than {@link Ext.form.CheckboxGroup#setValue} because only 1 value is allowed
101668      * for each name.
101669      * 
101670      * @param {Object} value The map from names to values to be set.
101671      * @return {Ext.form.CheckboxGroup} this
101672      */
101673     setValue: function(value) {
101674         var me = this;
101675         if (Ext.isObject(value)) {
101676             Ext.Object.each(value, function(name, cbValue) {
101677                 var radios = Ext.form.RadioManager.getWithValue(name, cbValue);
101678                 radios.each(function(cb) {
101679                     cb.setValue(true);
101680                 });
101681             });
101682         }
101683         return me;
101684     }
101685 });
101686
101687 /**
101688  * @private
101689  * Private utility class for managing all {@link Ext.form.field.Radio} fields grouped by name.
101690  */
101691 Ext.define('Ext.form.RadioManager', {
101692     extend: 'Ext.util.MixedCollection',
101693     singleton: true,
101694
101695     getByName: function(name) {
101696         return this.filterBy(function(item) {
101697             return item.name == name;
101698         });
101699     },
101700
101701     getWithValue: function(name, value) {
101702         return this.filterBy(function(item) {
101703             return item.name == name && item.inputValue == value;
101704         });
101705     },
101706
101707     getChecked: function(name) {
101708         return this.findBy(function(item) {
101709             return item.name == name && item.checked;
101710         });
101711     }
101712 });
101713
101714 /**
101715  * @class Ext.form.action.DirectLoad
101716  * @extends Ext.form.action.Load
101717  * <p>Provides {@link Ext.direct.Manager} support for loading form data.</p>
101718  * <p>This example illustrates usage of Ext.direct.Direct to <b>load</b> a form through Ext.Direct.</p>
101719  * <pre><code>
101720 var myFormPanel = new Ext.form.Panel({
101721     // configs for FormPanel
101722     title: 'Basic Information',
101723     renderTo: document.body,
101724     width: 300, height: 160,
101725     padding: 10,
101726
101727     // configs apply to child items
101728     defaults: {anchor: '100%'},
101729     defaultType: 'textfield',
101730     items: [{
101731         fieldLabel: 'Name',
101732         name: 'name'
101733     },{
101734         fieldLabel: 'Email',
101735         name: 'email'
101736     },{
101737         fieldLabel: 'Company',
101738         name: 'company'
101739     }],
101740
101741     // configs for BasicForm
101742     api: {
101743         // The server-side method to call for load() requests
101744         load: Profile.getBasicInfo,
101745         // The server-side must mark the submit handler as a 'formHandler'
101746         submit: Profile.updateBasicInfo
101747     },
101748     // specify the order for the passed params
101749     paramOrder: ['uid', 'foo']
101750 });
101751
101752 // load the form
101753 myFormPanel.getForm().load({
101754     // pass 2 arguments to server side getBasicInfo method (len=2)
101755     params: {
101756         foo: 'bar',
101757         uid: 34
101758     }
101759 });
101760  * </code></pre>
101761  * The data packet sent to the server will resemble something like:
101762  * <pre><code>
101763 [
101764     {
101765         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
101766         "data":[34,"bar"] // note the order of the params
101767     }
101768 ]
101769  * </code></pre>
101770  * The form will process a data packet returned by the server that is similar
101771  * to the following format:
101772  * <pre><code>
101773 [
101774     {
101775         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
101776         "result":{
101777             "success":true,
101778             "data":{
101779                 "name":"Fred Flintstone",
101780                 "company":"Slate Rock and Gravel",
101781                 "email":"fred.flintstone@slaterg.com"
101782             }
101783         }
101784     }
101785 ]
101786  * </code></pre>
101787  */
101788 Ext.define('Ext.form.action.DirectLoad', {
101789     extend:'Ext.form.action.Load',
101790     requires: ['Ext.direct.Manager'],
101791     alternateClassName: 'Ext.form.Action.DirectLoad',
101792     alias: 'formaction.directload',
101793
101794     type: 'directload',
101795
101796     run: function() {
101797         this.form.api.load.apply(window, this.getArgs());
101798     },
101799
101800     /**
101801      * @private
101802      * Build the arguments to be sent to the Direct call.
101803      * @return Array
101804      */
101805     getArgs: function() {
101806         var me = this,
101807             args = [],
101808             form = me.form,
101809             paramOrder = form.paramOrder,
101810             params = me.getParams(),
101811             i, len;
101812
101813         // If a paramOrder was specified, add the params into the argument list in that order.
101814         if (paramOrder) {
101815             for (i = 0, len = paramOrder.length; i < len; i++) {
101816                 args.push(params[paramOrder[i]]);
101817             }
101818         }
101819         // If paramsAsHash was specified, add all the params as a single object argument.
101820         else if (form.paramsAsHash) {
101821             args.push(params);
101822         }
101823
101824         // Add the callback and scope to the end of the arguments list
101825         args.push(me.onSuccess, me);
101826
101827         return args;
101828     },
101829
101830     // Direct actions have already been processed and therefore
101831     // we can directly set the result; Direct Actions do not have
101832     // a this.response property.
101833     processResponse: function(result) {
101834         return (this.result = result);
101835     },
101836
101837     onSuccess: function(result, trans) {
101838         if (trans.type == Ext.direct.Manager.self.exceptions.SERVER) {
101839             result = {};
101840         }
101841         this.callParent([result]);
101842     }
101843 });
101844
101845
101846
101847 /**
101848  * @class Ext.form.action.DirectSubmit
101849  * @extends Ext.form.action.Submit
101850  * <p>Provides Ext.direct support for submitting form data.</p>
101851  * <p>This example illustrates usage of Ext.direct.Direct to <b>submit</b> a form through Ext.Direct.</p>
101852  * <pre><code>
101853 var myFormPanel = new Ext.form.Panel({
101854     // configs for FormPanel
101855     title: 'Basic Information',
101856     renderTo: document.body,
101857     width: 300, height: 160,
101858     padding: 10,
101859     buttons:[{
101860         text: 'Submit',
101861         handler: function(){
101862             myFormPanel.getForm().submit({
101863                 params: {
101864                     foo: 'bar',
101865                     uid: 34
101866                 }
101867             });
101868         }
101869     }],
101870
101871     // configs apply to child items
101872     defaults: {anchor: '100%'},
101873     defaultType: 'textfield',
101874     items: [{
101875         fieldLabel: 'Name',
101876         name: 'name'
101877     },{
101878         fieldLabel: 'Email',
101879         name: 'email'
101880     },{
101881         fieldLabel: 'Company',
101882         name: 'company'
101883     }],
101884
101885     // configs for BasicForm
101886     api: {
101887         // The server-side method to call for load() requests
101888         load: Profile.getBasicInfo,
101889         // The server-side must mark the submit handler as a 'formHandler'
101890         submit: Profile.updateBasicInfo
101891     },
101892     // specify the order for the passed params
101893     paramOrder: ['uid', 'foo']
101894 });
101895  * </code></pre>
101896  * The data packet sent to the server will resemble something like:
101897  * <pre><code>
101898 {
101899     "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
101900     "result":{
101901         "success":true,
101902         "id":{
101903             "extAction":"Profile","extMethod":"updateBasicInfo",
101904             "extType":"rpc","extTID":"6","extUpload":"false",
101905             "name":"Aaron Conran","email":"aaron@sencha.com","company":"Sencha Inc."
101906         }
101907     }
101908 }
101909  * </code></pre>
101910  * The form will process a data packet returned by the server that is similar
101911  * to the following:
101912  * <pre><code>
101913 // sample success packet (batched requests)
101914 [
101915     {
101916         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
101917         "result":{
101918             "success":true
101919         }
101920     }
101921 ]
101922
101923 // sample failure packet (one request)
101924 {
101925         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
101926         "result":{
101927             "errors":{
101928                 "email":"already taken"
101929             },
101930             "success":false,
101931             "foo":"bar"
101932         }
101933 }
101934  * </code></pre>
101935  * Also see the discussion in {@link Ext.form.action.DirectLoad}.
101936  */
101937 Ext.define('Ext.form.action.DirectSubmit', {
101938     extend:'Ext.form.action.Submit',
101939     requires: ['Ext.direct.Manager'],
101940     alternateClassName: 'Ext.form.Action.DirectSubmit',
101941     alias: 'formaction.directsubmit',
101942
101943     type: 'directsubmit',
101944
101945     doSubmit: function() {
101946         var me = this,
101947             callback = Ext.Function.bind(me.onSuccess, me),
101948             formEl = me.buildForm();
101949         me.form.api.submit(formEl, callback, me);
101950         Ext.removeNode(formEl);
101951     },
101952
101953     // Direct actions have already been processed and therefore
101954     // we can directly set the result; Direct Actions do not have
101955     // a this.response property.
101956     processResponse: function(result) {
101957         return (this.result = result);
101958     },
101959
101960     onSuccess: function(response, trans) {
101961         if (trans.type === Ext.direct.Manager.self.exceptions.SERVER) {
101962             response = {};
101963         }
101964         this.callParent([response]);
101965     }
101966 });
101967
101968 /**
101969  * @class Ext.form.action.StandardSubmit
101970  * @extends Ext.form.action.Submit
101971  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s using a standard
101972  * <tt>&lt;form&gt;</tt> element submit. It does not handle the response from the submit.</p>
101973  * <p>If validation of the form fields fails, the Form's afterAction method
101974  * will be called. Otherwise, afterAction will not be called.</p>
101975  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
101976  * {@link Ext.form.Basic#submit submit}ting, when the form's {@link Ext.form.Basic#standardSubmit}
101977  * config option is <tt>true</tt>.</p>
101978  */
101979 Ext.define('Ext.form.action.StandardSubmit', {
101980     extend:'Ext.form.action.Submit',
101981     alias: 'formaction.standardsubmit',
101982
101983     /**
101984      * @cfg {String} target
101985      * Optional <tt>target</tt> attribute to be used for the form when submitting. If not specified,
101986      * the target will be the current window/frame.
101987      */
101988
101989     /**
101990      * @private
101991      * Perform the form submit. Creates and submits a temporary form element containing an input element for each
101992      * field value returned by {@link Ext.form.Basic#getValues}, plus any configured {@link #params params} or
101993      * {@link Ext.form.Basic#baseParams baseParams}.
101994      */
101995     doSubmit: function() {
101996         var form = this.buildForm();
101997         form.submit();
101998         Ext.removeNode(form);
101999     }
102000
102001 });
102002
102003 /**
102004  * @docauthor Robert Dougan <rob@sencha.com>
102005  *
102006  * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields. Also serves as a
102007  * parent class for {@link Ext.form.field.Radio radio buttons}.
102008  *
102009  * # Labeling
102010  *
102011  * In addition to the {@link Ext.form.Labelable standard field labeling options}, checkboxes
102012  * may be given an optional {@link #boxLabel} which will be displayed immediately after checkbox. Also see
102013  * {@link Ext.form.CheckboxGroup} for a convenient method of grouping related checkboxes.
102014  *
102015  * # Values
102016  *
102017  * The main value of a checkbox is a boolean, indicating whether or not the checkbox is checked.
102018  * The following values will check the checkbox:
102019  *
102020  * - `true`
102021  * - `'true'`
102022  * - `'1'`
102023  * - `'on'`
102024  *
102025  * Any other value will uncheck the checkbox.
102026  *
102027  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be
102028  * sent as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set
102029  * this value if you have multiple checkboxes with the same {@link #name}. If not specified, the value `on`
102030  * will be used.
102031  *
102032  * # Example usage
102033  *
102034  *     @example
102035  *     Ext.create('Ext.form.Panel', {
102036  *         bodyPadding: 10,
102037  *         width: 300,
102038  *         title: 'Pizza Order',
102039  *         items: [
102040  *             {
102041  *                 xtype: 'fieldcontainer',
102042  *                 fieldLabel: 'Toppings',
102043  *                 defaultType: 'checkboxfield',
102044  *                 items: [
102045  *                     {
102046  *                         boxLabel  : 'Anchovies',
102047  *                         name      : 'topping',
102048  *                         inputValue: '1',
102049  *                         id        : 'checkbox1'
102050  *                     }, {
102051  *                         boxLabel  : 'Artichoke Hearts',
102052  *                         name      : 'topping',
102053  *                         inputValue: '2',
102054  *                         checked   : true,
102055  *                         id        : 'checkbox2'
102056  *                     }, {
102057  *                         boxLabel  : 'Bacon',
102058  *                         name      : 'topping',
102059  *                         inputValue: '3',
102060  *                         id        : 'checkbox3'
102061  *                     }
102062  *                 ]
102063  *             }
102064  *         ],
102065  *         bbar: [
102066  *             {
102067  *                 text: 'Select Bacon',
102068  *                 handler: function() {
102069  *                     Ext.getCmp('checkbox3').setValue(true);
102070  *                 }
102071  *             },
102072  *             '-',
102073  *             {
102074  *                 text: 'Select All',
102075  *                 handler: function() {
102076  *                     Ext.getCmp('checkbox1').setValue(true);
102077  *                     Ext.getCmp('checkbox2').setValue(true);
102078  *                     Ext.getCmp('checkbox3').setValue(true);
102079  *                 }
102080  *             },
102081  *             {
102082  *                 text: 'Deselect All',
102083  *                 handler: function() {
102084  *                     Ext.getCmp('checkbox1').setValue(false);
102085  *                     Ext.getCmp('checkbox2').setValue(false);
102086  *                     Ext.getCmp('checkbox3').setValue(false);
102087  *                 }
102088  *             }
102089  *         ],
102090  *         renderTo: Ext.getBody()
102091  *     });
102092  */
102093 Ext.define('Ext.form.field.Checkbox', {
102094     extend: 'Ext.form.field.Base',
102095     alias: ['widget.checkboxfield', 'widget.checkbox'],
102096     alternateClassName: 'Ext.form.Checkbox',
102097     requires: ['Ext.XTemplate', 'Ext.form.CheckboxManager'],
102098
102099     // note: {id} here is really {inputId}, but {cmpId} is available
102100     fieldSubTpl: [
102101         '<tpl if="boxLabel && boxLabelAlign == \'before\'">',
102102             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
102103         '</tpl>',
102104         // Creates not an actual checkbox, but a button which is given aria role="checkbox" and
102105         // styled with a custom checkbox image. This allows greater control and consistency in
102106         // styling, and using a button allows it to gain focus and handle keyboard nav properly.
102107         '<input type="button" id="{id}" ',
102108             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
102109             'class="{fieldCls} {typeCls}" autocomplete="off" hidefocus="true" />',
102110         '<tpl if="boxLabel && boxLabelAlign == \'after\'">',
102111             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
102112         '</tpl>',
102113         {
102114             disableFormats: true,
102115             compiled: true
102116         }
102117     ],
102118
102119     isCheckbox: true,
102120
102121     /**
102122      * @cfg {String} [focusCls='x-form-cb-focus']
102123      * The CSS class to use when the checkbox receives focus
102124      */
102125     focusCls: Ext.baseCSSPrefix + 'form-cb-focus',
102126
102127     /**
102128      * @cfg {String} [fieldCls='x-form-field']
102129      * The default CSS class for the checkbox
102130      */
102131
102132     /**
102133      * @cfg {String} [fieldBodyCls='x-form-cb-wrap']
102134      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
102135      * .
102136      */
102137     fieldBodyCls: Ext.baseCSSPrefix + 'form-cb-wrap',
102138
102139     /**
102140      * @cfg {Boolean} checked
102141      * true if the checkbox should render initially checked
102142      */
102143     checked: false,
102144
102145     /**
102146      * @cfg {String} [checkedCls='x-form-cb-checked']
102147      * The CSS class added to the component's main element when it is in the checked state.
102148      */
102149     checkedCls: Ext.baseCSSPrefix + 'form-cb-checked',
102150
102151     /**
102152      * @cfg {String} boxLabel
102153      * An optional text label that will appear next to the checkbox. Whether it appears before or after the checkbox is
102154      * determined by the {@link #boxLabelAlign} config.
102155      */
102156
102157     /**
102158      * @cfg {String} [boxLabelCls='x-form-cb-label']
102159      * The CSS class to be applied to the {@link #boxLabel} element
102160      */
102161     boxLabelCls: Ext.baseCSSPrefix + 'form-cb-label',
102162
102163     /**
102164      * @cfg {String} boxLabelAlign
102165      * The position relative to the checkbox where the {@link #boxLabel} should appear. Recognized values are 'before'
102166      * and 'after'.
102167      */
102168     boxLabelAlign: 'after',
102169
102170     /**
102171      * @cfg {String} inputValue
102172      * The value that should go into the generated input element's value attribute and should be used as the parameter
102173      * value when submitting as part of a form.
102174      */
102175     inputValue: 'on',
102176
102177     /**
102178      * @cfg {String} uncheckedValue
102179      * If configured, this will be submitted as the checkbox's value during form submit if the checkbox is unchecked. By
102180      * default this is undefined, which results in nothing being submitted for the checkbox field when the form is
102181      * submitted (the default behavior of HTML checkboxes).
102182      */
102183
102184     /**
102185      * @cfg {Function} handler
102186      * A function called when the {@link #checked} value changes (can be used instead of handling the {@link #change
102187      * change event}).
102188      * @cfg {Ext.form.field.Checkbox} handler.checkbox The Checkbox being toggled.
102189      * @cfg {Boolean} handler.checked The new checked state of the checkbox.
102190      */
102191
102192     /**
102193      * @cfg {Object} scope
102194      * An object to use as the scope ('this' reference) of the {@link #handler} function (defaults to this Checkbox).
102195      */
102196
102197     // private overrides
102198     checkChangeEvents: [],
102199     inputType: 'checkbox',
102200     ariaRole: 'checkbox',
102201
102202     // private
102203     onRe: /^on$/i,
102204
102205     initComponent: function(){
102206         this.callParent(arguments);
102207         this.getManager().add(this);
102208     },
102209
102210     initValue: function() {
102211         var me = this,
102212             checked = !!me.checked;
102213
102214         /**
102215          * @property {Object} originalValue
102216          * The original value of the field as configured in the {@link #checked} configuration, or as loaded by the last
102217          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
102218          */
102219         me.originalValue = me.lastValue = checked;
102220
102221         // Set the initial checked state
102222         me.setValue(checked);
102223     },
102224
102225     // private
102226     onRender : function(ct, position) {
102227         var me = this;
102228
102229         /**
102230          * @property {Ext.Element} boxLabelEl
102231          * A reference to the label element created for the {@link #boxLabel}. Only present if the component has been
102232          * rendered and has a boxLabel configured.
102233          */
102234         me.addChildEls('boxLabelEl');
102235
102236         Ext.applyIf(me.subTplData, {
102237             boxLabel: me.boxLabel,
102238             boxLabelCls: me.boxLabelCls,
102239             boxLabelAlign: me.boxLabelAlign
102240         });
102241
102242         me.callParent(arguments);
102243     },
102244
102245     initEvents: function() {
102246         var me = this;
102247         me.callParent();
102248         me.mon(me.inputEl, 'click', me.onBoxClick, me);
102249     },
102250
102251     /**
102252      * @private Handle click on the checkbox button
102253      */
102254     onBoxClick: function(e) {
102255         var me = this;
102256         if (!me.disabled && !me.readOnly) {
102257             this.setValue(!this.checked);
102258         }
102259     },
102260
102261     /**
102262      * Returns the checked state of the checkbox.
102263      * @return {Boolean} True if checked, else false
102264      */
102265     getRawValue: function() {
102266         return this.checked;
102267     },
102268
102269     /**
102270      * Returns the checked state of the checkbox.
102271      * @return {Boolean} True if checked, else false
102272      */
102273     getValue: function() {
102274         return this.checked;
102275     },
102276
102277     /**
102278      * Returns the submit value for the checkbox which can be used when submitting forms.
102279      * @return {Boolean/Object} True if checked; otherwise either the {@link #uncheckedValue} or null.
102280      */
102281     getSubmitValue: function() {
102282         var unchecked = this.uncheckedValue,
102283             uncheckedVal = Ext.isDefined(unchecked) ? unchecked : null;
102284         return this.checked ? this.inputValue : uncheckedVal;
102285     },
102286
102287     /**
102288      * Sets the checked state of the checkbox.
102289      *
102290      * @param {Boolean/String/Number} value The following values will check the checkbox:
102291      * `true, 'true', '1', 1, or 'on'`, as well as a String that matches the {@link #inputValue}.
102292      * Any other value will uncheck the checkbox.
102293      * @return {Boolean} the new checked state of the checkbox
102294      */
102295     setRawValue: function(value) {
102296         var me = this,
102297             inputEl = me.inputEl,
102298             inputValue = me.inputValue,
102299             checked = (value === true || value === 'true' || value === '1' || value === 1 ||
102300                 (((Ext.isString(value) || Ext.isNumber(value)) && inputValue) ? value == inputValue : me.onRe.test(value)));
102301
102302         if (inputEl) {
102303             inputEl.dom.setAttribute('aria-checked', checked);
102304             me[checked ? 'addCls' : 'removeCls'](me.checkedCls);
102305         }
102306
102307         me.checked = me.rawValue = checked;
102308         return checked;
102309     },
102310
102311     /**
102312      * Sets the checked state of the checkbox, and invokes change detection.
102313      * @param {Boolean/String} checked The following values will check the checkbox: `true, 'true', '1', or 'on'`, as
102314      * well as a String that matches the {@link #inputValue}. Any other value will uncheck the checkbox.
102315      * @return {Ext.form.field.Checkbox} this
102316      */
102317     setValue: function(checked) {
102318         var me = this;
102319
102320         // If an array of strings is passed, find all checkboxes in the group with the same name as this
102321         // one and check all those whose inputValue is in the array, unchecking all the others. This is to
102322         // facilitate setting values from Ext.form.Basic#setValues, but is not publicly documented as we
102323         // don't want users depending on this behavior.
102324         if (Ext.isArray(checked)) {
102325             me.getManager().getByName(me.name).each(function(cb) {
102326                 cb.setValue(Ext.Array.contains(checked, cb.inputValue));
102327             });
102328         } else {
102329             me.callParent(arguments);
102330         }
102331
102332         return me;
102333     },
102334
102335     // private
102336     valueToRaw: function(value) {
102337         // No extra conversion for checkboxes
102338         return value;
102339     },
102340
102341     /**
102342      * @private
102343      * Called when the checkbox's checked state changes. Invokes the {@link #handler} callback
102344      * function if specified.
102345      */
102346     onChange: function(newVal, oldVal) {
102347         var me = this,
102348             handler = me.handler;
102349         if (handler) {
102350             handler.call(me.scope || me, me, newVal);
102351         }
102352         me.callParent(arguments);
102353     },
102354
102355     // inherit docs
102356     beforeDestroy: function(){
102357         this.callParent();
102358         this.getManager().removeAtKey(this.id);
102359     },
102360
102361     // inherit docs
102362     getManager: function() {
102363         return Ext.form.CheckboxManager;
102364     },
102365
102366     onEnable: function() {
102367         var me = this,
102368             inputEl = me.inputEl;
102369         me.callParent();
102370         if (inputEl) {
102371             // Can still be disabled if the field is readOnly
102372             inputEl.dom.disabled = me.readOnly;
102373         }
102374     },
102375
102376     setReadOnly: function(readOnly) {
102377         var me = this,
102378             inputEl = me.inputEl;
102379         if (inputEl) {
102380             // Set the button to disabled when readonly
102381             inputEl.dom.disabled = readOnly || me.disabled;
102382         }
102383         me.readOnly = readOnly;
102384     },
102385
102386     // Calculates and returns the natural width of the bodyEl. It's possible that the initial rendering will
102387     // cause the boxLabel to wrap and give us a bad width, so we must prevent wrapping while measuring.
102388     getBodyNaturalWidth: function() {
102389         var me = this,
102390             bodyEl = me.bodyEl,
102391             ws = 'white-space',
102392             width;
102393         bodyEl.setStyle(ws, 'nowrap');
102394         width = bodyEl.getWidth();
102395         bodyEl.setStyle(ws, '');
102396         return width;
102397     }
102398
102399 });
102400
102401 /**
102402  * @private
102403  * @class Ext.layout.component.field.Trigger
102404  * @extends Ext.layout.component.field.Field
102405  * Layout class for {@link Ext.form.field.Trigger} fields. Adjusts the input field size to accommodate
102406  * the trigger button(s).
102407  * @private
102408  */
102409
102410 Ext.define('Ext.layout.component.field.Trigger', {
102411
102412     /* Begin Definitions */
102413
102414     alias: ['layout.triggerfield'],
102415
102416     extend: 'Ext.layout.component.field.Field',
102417
102418     /* End Definitions */
102419
102420     type: 'triggerfield',
102421
102422     sizeBodyContents: function(width, height) {
102423         var me = this,
102424             owner = me.owner,
102425             inputEl = owner.inputEl,
102426             triggerWrap = owner.triggerWrap,
102427             triggerWidth = owner.getTriggerWidth();
102428
102429         // If we or our ancestor is hidden, we can get a triggerWidth calculation
102430         // of 0.  We don't want to resize in this case.
102431         if (owner.hideTrigger || owner.readOnly || triggerWidth > 0) {
102432             // Decrease the field's width by the width of the triggers. Both the field and the triggerWrap
102433             // are floated left in CSS so they'll stack up side by side.
102434             me.setElementSize(inputEl, Ext.isNumber(width) ? width - triggerWidth : width);
102435     
102436             // Explicitly set the triggerWrap's width, to prevent wrapping
102437             triggerWrap.setWidth(triggerWidth);
102438         }
102439     }
102440 });
102441 /**
102442  * A mechanism for displaying data using custom layout templates and formatting.
102443  *
102444  * The View uses an {@link Ext.XTemplate} as its internal templating mechanism, and is bound to an
102445  * {@link Ext.data.Store} so that as the data in the store changes the view is automatically updated
102446  * to reflect the changes. The view also provides built-in behavior for many common events that can
102447  * occur for its contained items including click, doubleclick, mouseover, mouseout, etc. as well as a
102448  * built-in selection model. **In order to use these features, an {@link #itemSelector} config must
102449  * be provided for the DataView to determine what nodes it will be working with.**
102450  *
102451  * The example below binds a View to a {@link Ext.data.Store} and renders it into an {@link Ext.panel.Panel}.
102452  *
102453  *     @example
102454  *     Ext.define('Image', {
102455  *         extend: 'Ext.data.Model',
102456  *         fields: [
102457  *             { name:'src', type:'string' },
102458  *             { name:'caption', type:'string' }
102459  *         ]
102460  *     });
102461  *
102462  *     Ext.create('Ext.data.Store', {
102463  *         id:'imagesStore',
102464  *         model: 'Image',
102465  *         data: [
102466  *             { src:'http://www.sencha.com/img/20110215-feat-drawing.png', caption:'Drawing & Charts' },
102467  *             { src:'http://www.sencha.com/img/20110215-feat-data.png', caption:'Advanced Data' },
102468  *             { src:'http://www.sencha.com/img/20110215-feat-html5.png', caption:'Overhauled Theme' },
102469  *             { src:'http://www.sencha.com/img/20110215-feat-perf.png', caption:'Performance Tuned' }
102470  *         ]
102471  *     });
102472  *
102473  *     var imageTpl = new Ext.XTemplate(
102474  *         '<tpl for=".">',
102475  *             '<div style="margin-bottom: 10px;" class="thumb-wrap">',
102476  *               '<img src="{src}" />',
102477  *               '<br/><span>{caption}</span>',
102478  *             '</div>',
102479  *         '</tpl>'
102480  *     );
102481  *
102482  *     Ext.create('Ext.view.View', {
102483  *         store: Ext.data.StoreManager.lookup('imagesStore'),
102484  *         tpl: imageTpl,
102485  *         itemSelector: 'div.thumb-wrap',
102486  *         emptyText: 'No images available',
102487  *         renderTo: Ext.getBody()
102488  *     });
102489  */
102490 Ext.define('Ext.view.View', {
102491     extend: 'Ext.view.AbstractView',
102492     alternateClassName: 'Ext.DataView',
102493     alias: 'widget.dataview',
102494
102495     inheritableStatics: {
102496         EventMap: {
102497             mousedown: 'MouseDown',
102498             mouseup: 'MouseUp',
102499             click: 'Click',
102500             dblclick: 'DblClick',
102501             contextmenu: 'ContextMenu',
102502             mouseover: 'MouseOver',
102503             mouseout: 'MouseOut',
102504             mouseenter: 'MouseEnter',
102505             mouseleave: 'MouseLeave',
102506             keydown: 'KeyDown',
102507             focus: 'Focus'
102508         }
102509     },
102510
102511     addCmpEvents: function() {
102512         this.addEvents(
102513             /**
102514              * @event beforeitemmousedown
102515              * Fires before the mousedown event on an item is processed. Returns false to cancel the default action.
102516              * @param {Ext.view.View} this
102517              * @param {Ext.data.Model} record The record that belongs to the item
102518              * @param {HTMLElement} item The item's element
102519              * @param {Number} index The item's index
102520              * @param {Ext.EventObject} e The raw event object
102521              */
102522             'beforeitemmousedown',
102523             /**
102524              * @event beforeitemmouseup
102525              * Fires before the mouseup event on an item is processed. Returns false to cancel the default action.
102526              * @param {Ext.view.View} this
102527              * @param {Ext.data.Model} record The record that belongs to the item
102528              * @param {HTMLElement} item The item's element
102529              * @param {Number} index The item's index
102530              * @param {Ext.EventObject} e The raw event object
102531              */
102532             'beforeitemmouseup',
102533             /**
102534              * @event beforeitemmouseenter
102535              * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action.
102536              * @param {Ext.view.View} this
102537              * @param {Ext.data.Model} record The record that belongs to the item
102538              * @param {HTMLElement} item The item's element
102539              * @param {Number} index The item's index
102540              * @param {Ext.EventObject} e The raw event object
102541              */
102542             'beforeitemmouseenter',
102543             /**
102544              * @event beforeitemmouseleave
102545              * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action.
102546              * @param {Ext.view.View} this
102547              * @param {Ext.data.Model} record The record that belongs to the item
102548              * @param {HTMLElement} item The item's element
102549              * @param {Number} index The item's index
102550              * @param {Ext.EventObject} e The raw event object
102551              */
102552             'beforeitemmouseleave',
102553             /**
102554              * @event beforeitemclick
102555              * Fires before the click event on an item is processed. Returns false to cancel the default action.
102556              * @param {Ext.view.View} this
102557              * @param {Ext.data.Model} record The record that belongs to the item
102558              * @param {HTMLElement} item The item's element
102559              * @param {Number} index The item's index
102560              * @param {Ext.EventObject} e The raw event object
102561              */
102562             'beforeitemclick',
102563             /**
102564              * @event beforeitemdblclick
102565              * Fires before the dblclick event on an item is processed. Returns false to cancel the default action.
102566              * @param {Ext.view.View} this
102567              * @param {Ext.data.Model} record The record that belongs to the item
102568              * @param {HTMLElement} item The item's element
102569              * @param {Number} index The item's index
102570              * @param {Ext.EventObject} e The raw event object
102571              */
102572             'beforeitemdblclick',
102573             /**
102574              * @event beforeitemcontextmenu
102575              * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action.
102576              * @param {Ext.view.View} this
102577              * @param {Ext.data.Model} record The record that belongs to the item
102578              * @param {HTMLElement} item The item's element
102579              * @param {Number} index The item's index
102580              * @param {Ext.EventObject} e The raw event object
102581              */
102582             'beforeitemcontextmenu',
102583             /**
102584              * @event beforeitemkeydown
102585              * Fires before the keydown event on an item is processed. Returns false to cancel the default action.
102586              * @param {Ext.view.View} this
102587              * @param {Ext.data.Model} record The record that belongs to the item
102588              * @param {HTMLElement} item The item's element
102589              * @param {Number} index The item's index
102590              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
102591              */
102592             'beforeitemkeydown',
102593             /**
102594              * @event itemmousedown
102595              * Fires when there is a mouse down on an item
102596              * @param {Ext.view.View} this
102597              * @param {Ext.data.Model} record The record that belongs to the item
102598              * @param {HTMLElement} item The item's element
102599              * @param {Number} index The item's index
102600              * @param {Ext.EventObject} e The raw event object
102601              */
102602             'itemmousedown',
102603             /**
102604              * @event itemmouseup
102605              * Fires when there is a mouse up on an item
102606              * @param {Ext.view.View} this
102607              * @param {Ext.data.Model} record The record that belongs to the item
102608              * @param {HTMLElement} item The item's element
102609              * @param {Number} index The item's index
102610              * @param {Ext.EventObject} e The raw event object
102611              */
102612             'itemmouseup',
102613             /**
102614              * @event itemmouseenter
102615              * Fires when the mouse enters an item.
102616              * @param {Ext.view.View} this
102617              * @param {Ext.data.Model} record The record that belongs to the item
102618              * @param {HTMLElement} item The item's element
102619              * @param {Number} index The item's index
102620              * @param {Ext.EventObject} e The raw event object
102621              */
102622             'itemmouseenter',
102623             /**
102624              * @event itemmouseleave
102625              * Fires when the mouse leaves an item.
102626              * @param {Ext.view.View} this
102627              * @param {Ext.data.Model} record The record that belongs to the item
102628              * @param {HTMLElement} item The item's element
102629              * @param {Number} index The item's index
102630              * @param {Ext.EventObject} e The raw event object
102631              */
102632             'itemmouseleave',
102633             /**
102634              * @event itemclick
102635              * Fires when an item is clicked.
102636              * @param {Ext.view.View} this
102637              * @param {Ext.data.Model} record The record that belongs to the item
102638              * @param {HTMLElement} item The item's element
102639              * @param {Number} index The item's index
102640              * @param {Ext.EventObject} e The raw event object
102641              */
102642             'itemclick',
102643             /**
102644              * @event itemdblclick
102645              * Fires when an item is double clicked.
102646              * @param {Ext.view.View} this
102647              * @param {Ext.data.Model} record The record that belongs to the item
102648              * @param {HTMLElement} item The item's element
102649              * @param {Number} index The item's index
102650              * @param {Ext.EventObject} e The raw event object
102651              */
102652             'itemdblclick',
102653             /**
102654              * @event itemcontextmenu
102655              * Fires when an item is right clicked.
102656              * @param {Ext.view.View} this
102657              * @param {Ext.data.Model} record The record that belongs to the item
102658              * @param {HTMLElement} item The item's element
102659              * @param {Number} index The item's index
102660              * @param {Ext.EventObject} e The raw event object
102661              */
102662             'itemcontextmenu',
102663             /**
102664              * @event itemkeydown
102665              * Fires when a key is pressed while an item is currently selected.
102666              * @param {Ext.view.View} this
102667              * @param {Ext.data.Model} record The record that belongs to the item
102668              * @param {HTMLElement} item The item's element
102669              * @param {Number} index The item's index
102670              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
102671              */
102672             'itemkeydown',
102673             /**
102674              * @event beforecontainermousedown
102675              * Fires before the mousedown 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             'beforecontainermousedown',
102680             /**
102681              * @event beforecontainermouseup
102682              * Fires before the mouseup 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             'beforecontainermouseup',
102687             /**
102688              * @event beforecontainermouseover
102689              * Fires before the mouseover 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             'beforecontainermouseover',
102694             /**
102695              * @event beforecontainermouseout
102696              * Fires before the mouseout 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             'beforecontainermouseout',
102701             /**
102702              * @event beforecontainerclick
102703              * Fires before the click 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
102706              */
102707             'beforecontainerclick',
102708             /**
102709              * @event beforecontainerdblclick
102710              * Fires before the dblclick event on the container is processed. Returns false to cancel the default action.
102711              * @param {Ext.view.View} this
102712              * @param {Ext.EventObject} e The raw event object
102713              */
102714             'beforecontainerdblclick',
102715             /**
102716              * @event beforecontainercontextmenu
102717              * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action.
102718              * @param {Ext.view.View} this
102719              * @param {Ext.EventObject} e The raw event object
102720              */
102721             'beforecontainercontextmenu',
102722             /**
102723              * @event beforecontainerkeydown
102724              * Fires before the keydown event on the container is processed. Returns false to cancel the default action.
102725              * @param {Ext.view.View} this
102726              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
102727              */
102728             'beforecontainerkeydown',
102729             /**
102730              * @event containermouseup
102731              * Fires when there is a mouse up on the container
102732              * @param {Ext.view.View} this
102733              * @param {Ext.EventObject} e The raw event object
102734              */
102735             'containermouseup',
102736             /**
102737              * @event containermouseover
102738              * Fires when you move the mouse over the container.
102739              * @param {Ext.view.View} this
102740              * @param {Ext.EventObject} e The raw event object
102741              */
102742             'containermouseover',
102743             /**
102744              * @event containermouseout
102745              * Fires when you move the mouse out of the container.
102746              * @param {Ext.view.View} this
102747              * @param {Ext.EventObject} e The raw event object
102748              */
102749             'containermouseout',
102750             /**
102751              * @event containerclick
102752              * Fires when the container is clicked.
102753              * @param {Ext.view.View} this
102754              * @param {Ext.EventObject} e The raw event object
102755              */
102756             'containerclick',
102757             /**
102758              * @event containerdblclick
102759              * Fires when the container is double clicked.
102760              * @param {Ext.view.View} this
102761              * @param {Ext.EventObject} e The raw event object
102762              */
102763             'containerdblclick',
102764             /**
102765              * @event containercontextmenu
102766              * Fires when the container is right clicked.
102767              * @param {Ext.view.View} this
102768              * @param {Ext.EventObject} e The raw event object
102769              */
102770             'containercontextmenu',
102771             /**
102772              * @event containerkeydown
102773              * Fires when a key is pressed while the container is focused, and no item is currently selected.
102774              * @param {Ext.view.View} this
102775              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
102776              */
102777             'containerkeydown',
102778
102779             /**
102780              * @event selectionchange
102781              * Fires when the selected nodes change. Relayed event from the underlying selection model.
102782              * @param {Ext.view.View} this
102783              * @param {HTMLElement[]} selections Array of the selected nodes
102784              */
102785             'selectionchange',
102786             /**
102787              * @event beforeselect
102788              * Fires before a selection is made. If any handlers return false, the selection is cancelled.
102789              * @param {Ext.view.View} this
102790              * @param {HTMLElement} node The node to be selected
102791              * @param {HTMLElement[]} selections Array of currently selected nodes
102792              */
102793             'beforeselect'
102794         );
102795     },
102796     // private
102797     afterRender: function(){
102798         var me = this,
102799             listeners;
102800
102801         me.callParent();
102802
102803         listeners = {
102804             scope: me,
102805             /*
102806              * We need to make copies of this since some of the events fired here will end up triggering
102807              * a new event to be called and the shared event object will be mutated. In future we should
102808              * investigate if there are any issues with creating a new event object for each event that
102809              * is fired.
102810              */
102811             freezeEvent: true,
102812             click: me.handleEvent,
102813             mousedown: me.handleEvent,
102814             mouseup: me.handleEvent,
102815             dblclick: me.handleEvent,
102816             contextmenu: me.handleEvent,
102817             mouseover: me.handleEvent,
102818             mouseout: me.handleEvent,
102819             keydown: me.handleEvent
102820         };
102821
102822         me.mon(me.getTargetEl(), listeners);
102823
102824         if (me.store) {
102825             me.bindStore(me.store, true);
102826         }
102827     },
102828
102829     handleEvent: function(e) {
102830         if (this.processUIEvent(e) !== false) {
102831             this.processSpecialEvent(e);
102832         }
102833     },
102834
102835     // Private template method
102836     processItemEvent: Ext.emptyFn,
102837     processContainerEvent: Ext.emptyFn,
102838     processSpecialEvent: Ext.emptyFn,
102839
102840     /*
102841      * Returns true if this mouseover/out event is still over the overItem.
102842      */
102843     stillOverItem: function (event, overItem) {
102844         var nowOver;
102845
102846         // There is this weird bug when you hover over the border of a cell it is saying
102847         // the target is the table.
102848         // BrowserBug: IE6 & 7. If me.mouseOverItem has been removed and is no longer
102849         // in the DOM then accessing .offsetParent will throw an "Unspecified error." exception.
102850         // typeof'ng and checking to make sure the offsetParent is an object will NOT throw
102851         // this hard exception.
102852         if (overItem && typeof(overItem.offsetParent) === "object") {
102853             // mouseout : relatedTarget == nowOver, target == wasOver
102854             // mouseover: relatedTarget == wasOver, target == nowOver
102855             nowOver = (event.type == 'mouseout') ? event.getRelatedTarget() : event.getTarget();
102856             return Ext.fly(overItem).contains(nowOver);
102857         }
102858
102859         return false;
102860     },
102861
102862     processUIEvent: function(e) {
102863         var me = this,
102864             item = e.getTarget(me.getItemSelector(), me.getTargetEl()),
102865             map = this.statics().EventMap,
102866             index, record,
102867             type = e.type,
102868             overItem = me.mouseOverItem,
102869             newType;
102870
102871         if (!item) {
102872             if (type == 'mouseover' && me.stillOverItem(e, overItem)) {
102873                 item = overItem;
102874             }
102875
102876             // Try to get the selected item to handle the keydown event, otherwise we'll just fire a container keydown event
102877             if (type == 'keydown') {
102878                 record = me.getSelectionModel().getLastSelected();
102879                 if (record) {
102880                     item = me.getNode(record);
102881                 }
102882             }
102883         }
102884
102885         if (item) {
102886             index = me.indexOf(item);
102887             if (!record) {
102888                 record = me.getRecord(item);
102889             }
102890
102891             if (me.processItemEvent(record, item, index, e) === false) {
102892                 return false;
102893             }
102894
102895             newType = me.isNewItemEvent(item, e);
102896             if (newType === false) {
102897                 return false;
102898             }
102899
102900             if (
102901                 (me['onBeforeItem' + map[newType]](record, item, index, e) === false) ||
102902                 (me.fireEvent('beforeitem' + newType, me, record, item, index, e) === false) ||
102903                 (me['onItem' + map[newType]](record, item, index, e) === false)
102904             ) {
102905                 return false;
102906             }
102907
102908             me.fireEvent('item' + newType, me, record, item, index, e);
102909         }
102910         else {
102911             if (
102912                 (me.processContainerEvent(e) === false) ||
102913                 (me['onBeforeContainer' + map[type]](e) === false) ||
102914                 (me.fireEvent('beforecontainer' + type, me, e) === false) ||
102915                 (me['onContainer' + map[type]](e) === false)
102916             ) {
102917                 return false;
102918             }
102919
102920             me.fireEvent('container' + type, me, e);
102921         }
102922
102923         return true;
102924     },
102925
102926     isNewItemEvent: function (item, e) {
102927         var me = this,
102928             overItem = me.mouseOverItem,
102929             type = e.type;
102930
102931         switch (type) {
102932             case 'mouseover':
102933                 if (item === overItem) {
102934                     return false;
102935                 }
102936                 me.mouseOverItem = item;
102937                 return 'mouseenter';
102938
102939             case 'mouseout':
102940                 // If the currently mouseovered item contains the mouseover target, it's *NOT* a mouseleave
102941                 if (me.stillOverItem(e, overItem)) {
102942                     return false;
102943                 }
102944                 me.mouseOverItem = null;
102945                 return 'mouseleave';
102946         }
102947         return type;
102948     },
102949
102950     // private
102951     onItemMouseEnter: function(record, item, index, e) {
102952         if (this.trackOver) {
102953             this.highlightItem(item);
102954         }
102955     },
102956
102957     // private
102958     onItemMouseLeave : function(record, item, index, e) {
102959         if (this.trackOver) {
102960             this.clearHighlight();
102961         }
102962     },
102963
102964     // @private, template methods
102965     onItemMouseDown: Ext.emptyFn,
102966     onItemMouseUp: Ext.emptyFn,
102967     onItemFocus: Ext.emptyFn,
102968     onItemClick: Ext.emptyFn,
102969     onItemDblClick: Ext.emptyFn,
102970     onItemContextMenu: Ext.emptyFn,
102971     onItemKeyDown: Ext.emptyFn,
102972     onBeforeItemMouseDown: Ext.emptyFn,
102973     onBeforeItemMouseUp: Ext.emptyFn,
102974     onBeforeItemFocus: Ext.emptyFn,
102975     onBeforeItemMouseEnter: Ext.emptyFn,
102976     onBeforeItemMouseLeave: Ext.emptyFn,
102977     onBeforeItemClick: Ext.emptyFn,
102978     onBeforeItemDblClick: Ext.emptyFn,
102979     onBeforeItemContextMenu: Ext.emptyFn,
102980     onBeforeItemKeyDown: Ext.emptyFn,
102981
102982     // @private, template methods
102983     onContainerMouseDown: Ext.emptyFn,
102984     onContainerMouseUp: Ext.emptyFn,
102985     onContainerMouseOver: Ext.emptyFn,
102986     onContainerMouseOut: Ext.emptyFn,
102987     onContainerClick: Ext.emptyFn,
102988     onContainerDblClick: Ext.emptyFn,
102989     onContainerContextMenu: Ext.emptyFn,
102990     onContainerKeyDown: Ext.emptyFn,
102991     onBeforeContainerMouseDown: Ext.emptyFn,
102992     onBeforeContainerMouseUp: Ext.emptyFn,
102993     onBeforeContainerMouseOver: Ext.emptyFn,
102994     onBeforeContainerMouseOut: Ext.emptyFn,
102995     onBeforeContainerClick: Ext.emptyFn,
102996     onBeforeContainerDblClick: Ext.emptyFn,
102997     onBeforeContainerContextMenu: Ext.emptyFn,
102998     onBeforeContainerKeyDown: Ext.emptyFn,
102999
103000     /**
103001      * Highlights a given item in the DataView. This is called by the mouseover handler if {@link #overItemCls}
103002      * and {@link #trackOver} are configured, but can also be called manually by other code, for instance to
103003      * handle stepping through the list via keyboard navigation.
103004      * @param {HTMLElement} item The item to highlight
103005      */
103006     highlightItem: function(item) {
103007         var me = this;
103008         me.clearHighlight();
103009         me.highlightedItem = item;
103010         Ext.fly(item).addCls(me.overItemCls);
103011     },
103012
103013     /**
103014      * Un-highlights the currently highlighted item, if any.
103015      */
103016     clearHighlight: function() {
103017         var me = this,
103018             highlighted = me.highlightedItem;
103019
103020         if (highlighted) {
103021             Ext.fly(highlighted).removeCls(me.overItemCls);
103022             delete me.highlightedItem;
103023         }
103024     },
103025
103026     refresh: function() {
103027         var me = this;
103028         me.clearHighlight();
103029         me.callParent(arguments);
103030         if (!me.isFixedHeight()) {
103031             me.doComponentLayout();
103032         }
103033     }
103034 });
103035 /**
103036  * Component layout for {@link Ext.view.BoundList}. Handles constraining the height to the configured maxHeight.
103037  * @class Ext.layout.component.BoundList
103038  * @extends Ext.layout.component.Component
103039  * @private
103040  */
103041 Ext.define('Ext.layout.component.BoundList', {
103042     extend: 'Ext.layout.component.Component',
103043     alias: 'layout.boundlist',
103044
103045     type: 'component',
103046
103047     beforeLayout: function() {
103048         return this.callParent(arguments) || this.owner.refreshed > 0;
103049     },
103050
103051     onLayout : function(width, height) {
103052         var me = this,
103053             owner = me.owner,
103054             floating = owner.floating,
103055             el = owner.el,
103056             xy = el.getXY(),
103057             isNumber = Ext.isNumber,
103058             minWidth, maxWidth, minHeight, maxHeight,
103059             naturalWidth, naturalHeight, constrainedWidth, constrainedHeight, undef;
103060
103061         if (floating) {
103062             // Position offscreen so the natural width is not affected by the viewport's right edge
103063             el.setXY([-9999,-9999]);
103064         }
103065
103066         // Calculate initial layout
103067         me.setTargetSize(width, height);
103068
103069         // Handle min/maxWidth for auto-width
103070         if (!isNumber(width)) {
103071             minWidth = owner.minWidth;
103072             maxWidth = owner.maxWidth;
103073             if (isNumber(minWidth) || isNumber(maxWidth)) {
103074                 naturalWidth = el.getWidth();
103075                 if (naturalWidth < minWidth) {
103076                     constrainedWidth = minWidth;
103077                 }
103078                 else if (naturalWidth > maxWidth) {
103079                     constrainedWidth = maxWidth;
103080                 }
103081                 if (constrainedWidth) {
103082                     me.setTargetSize(constrainedWidth);
103083                 }
103084             }
103085         }
103086         // Handle min/maxHeight for auto-height
103087         if (!isNumber(height)) {
103088             minHeight = owner.minHeight;
103089             maxHeight = owner.maxHeight;
103090             if (isNumber(minHeight) || isNumber(maxHeight)) {
103091                 naturalHeight = el.getHeight();
103092                 if (naturalHeight < minHeight) {
103093                     constrainedHeight = minHeight;
103094                 }
103095                 else if (naturalHeight > maxHeight) {
103096                     constrainedHeight = maxHeight;
103097                 }
103098                 if (constrainedHeight) {
103099                     me.setTargetSize(undef, constrainedHeight);
103100                 }
103101             }
103102         }
103103
103104         if (floating) {
103105             // Restore position
103106             el.setXY(xy);
103107         }
103108     },
103109
103110     afterLayout: function() {
103111         var me = this,
103112             toolbar = me.owner.pagingToolbar;
103113         me.callParent();
103114         if (toolbar) {
103115             toolbar.doComponentLayout();
103116         }
103117     },
103118
103119     setTargetSize : function(width, height) {
103120         var me = this,
103121             owner = me.owner,
103122             listHeight = null,
103123             toolbar;
103124
103125         // Size the listEl
103126         if (Ext.isNumber(height)) {
103127             listHeight = height - owner.el.getFrameWidth('tb');
103128             toolbar = owner.pagingToolbar;
103129             if (toolbar) {
103130                 listHeight -= toolbar.getHeight();
103131             }
103132         }
103133         me.setElementSize(owner.listEl, null, listHeight);
103134
103135         me.callParent(arguments);
103136     }
103137
103138 });
103139
103140 /**
103141  * A simple class that renders text directly into a toolbar.
103142  *
103143  *     @example
103144  *     Ext.create('Ext.panel.Panel', {
103145  *         title: 'Panel with TextItem',
103146  *         width: 300,
103147  *         height: 200,
103148  *         tbar: [
103149  *             { xtype: 'tbtext', text: 'Sample Text Item' }
103150  *         ],
103151  *         renderTo: Ext.getBody()
103152  *     });
103153  *
103154  * @constructor
103155  * Creates a new TextItem
103156  * @param {Object} text A text string, or a config object containing a <tt>text</tt> property
103157  */
103158 Ext.define('Ext.toolbar.TextItem', {
103159     extend: 'Ext.toolbar.Item',
103160     requires: ['Ext.XTemplate'],
103161     alias: 'widget.tbtext',
103162     alternateClassName: 'Ext.Toolbar.TextItem',
103163
103164     /**
103165      * @cfg {String} text The text to be used as innerHTML (html tags are accepted)
103166      */
103167     text: '',
103168
103169     renderTpl: '{text}',
103170     //
103171     baseCls: Ext.baseCSSPrefix + 'toolbar-text',
103172
103173     onRender : function() {
103174         Ext.apply(this.renderData, {
103175             text: this.text
103176         });
103177         this.callParent(arguments);
103178     },
103179
103180     /**
103181      * Updates this item's text, setting the text to be used as innerHTML.
103182      * @param {String} t The text to display (html accepted).
103183      */
103184     setText : function(t) {
103185         if (this.rendered) {
103186             this.el.update(t);
103187             this.ownerCt.doLayout(); // In case an empty text item (centered at zero height) receives new text.
103188         } else {
103189             this.text = t;
103190         }
103191     }
103192 });
103193 /**
103194  * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
103195  * The trigger has no default action, so you must assign a function to implement the trigger click handler by overriding
103196  * {@link #onTriggerClick}. You can create a Trigger field directly, as it renders exactly like a combobox for which you
103197  * can provide a custom implementation.
103198  *
103199  * For example:
103200  *
103201  *     @example
103202  *     Ext.define('Ext.ux.CustomTrigger', {
103203  *         extend: 'Ext.form.field.Trigger',
103204  *         alias: 'widget.customtrigger',
103205  *
103206  *         // override onTriggerClick
103207  *         onTriggerClick: function() {
103208  *             Ext.Msg.alert('Status', 'You clicked my trigger!');
103209  *         }
103210  *     });
103211  *
103212  *     Ext.create('Ext.form.FormPanel', {
103213  *         title: 'Form with TriggerField',
103214  *         bodyPadding: 5,
103215  *         width: 350,
103216  *         renderTo: Ext.getBody(),
103217  *         items:[{
103218  *             xtype: 'customtrigger',
103219  *             fieldLabel: 'Sample Trigger',
103220  *             emptyText: 'click the trigger',
103221  *         }]
103222  *     });
103223  *
103224  * However, in general you will most likely want to use Trigger as the base class for a reusable component.
103225  * {@link Ext.form.field.Date} and {@link Ext.form.field.ComboBox} are perfect examples of this.
103226  */
103227 Ext.define('Ext.form.field.Trigger', {
103228     extend:'Ext.form.field.Text',
103229     alias: ['widget.triggerfield', 'widget.trigger'],
103230     requires: ['Ext.DomHelper', 'Ext.util.ClickRepeater', 'Ext.layout.component.field.Trigger'],
103231     alternateClassName: ['Ext.form.TriggerField', 'Ext.form.TwinTriggerField', 'Ext.form.Trigger'],
103232
103233     // note: {id} here is really {inputId}, but {cmpId} is available
103234     fieldSubTpl: [
103235         '<input id="{id}" type="{type}" ',
103236             '<tpl if="name">name="{name}" </tpl>',
103237             '<tpl if="size">size="{size}" </tpl>',
103238             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
103239             'class="{fieldCls} {typeCls}" autocomplete="off" />',
103240         '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
103241             '{triggerEl}',
103242             '<div class="{clearCls}" role="presentation"></div>',
103243         '</div>',
103244         {
103245             compiled: true,
103246             disableFormats: true
103247         }
103248     ],
103249
103250     /**
103251      * @cfg {String} triggerCls
103252      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
103253      * by default and triggerCls will be **appended** if specified.
103254      */
103255
103256     /**
103257      * @cfg {String} [triggerBaseCls='x-form-trigger']
103258      * The base CSS class that is always added to the trigger button. The {@link #triggerCls} will be appended in
103259      * addition to this class.
103260      */
103261     triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger',
103262
103263     /**
103264      * @cfg {String} [triggerWrapCls='x-form-trigger-wrap']
103265      * The CSS class that is added to the div wrapping the trigger button(s).
103266      */
103267     triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap',
103268
103269     /**
103270      * @cfg {Boolean} hideTrigger
103271      * true to hide the trigger element and display only the base text field
103272      */
103273     hideTrigger: false,
103274
103275     /**
103276      * @cfg {Boolean} editable
103277      * false to prevent the user from typing text directly into the field; the field can only have its value set via an
103278      * action invoked by the trigger.
103279      */
103280     editable: true,
103281
103282     /**
103283      * @cfg {Boolean} readOnly
103284      * true to prevent the user from changing the field, and hides the trigger. Supercedes the editable and hideTrigger
103285      * options if the value is true.
103286      */
103287     readOnly: false,
103288
103289     /**
103290      * @cfg {Boolean} [selectOnFocus=false]
103291      * true to select any existing text in the field immediately on focus. Only applies when
103292      * {@link #editable editable} = true
103293      */
103294
103295     /**
103296      * @cfg {Boolean} repeatTriggerClick
103297      * true to attach a {@link Ext.util.ClickRepeater click repeater} to the trigger.
103298      */
103299     repeatTriggerClick: false,
103300
103301
103302     /**
103303      * @hide
103304      * @method autoSize
103305      */
103306     autoSize: Ext.emptyFn,
103307     // private
103308     monitorTab: true,
103309     // private
103310     mimicing: false,
103311     // private
103312     triggerIndexRe: /trigger-index-(\d+)/,
103313
103314     componentLayout: 'triggerfield',
103315
103316     initComponent: function() {
103317         this.wrapFocusCls = this.triggerWrapCls + '-focus';
103318         this.callParent(arguments);
103319     },
103320
103321     // private
103322     onRender: function(ct, position) {
103323         var me = this,
103324             triggerCls,
103325             triggerBaseCls = me.triggerBaseCls,
103326             triggerWrapCls = me.triggerWrapCls,
103327             triggerConfigs = [],
103328             i;
103329
103330         // triggerCls is a synonym for trigger1Cls, so copy it.
103331         // TODO this trigger<n>Cls API design doesn't feel clean, especially where it butts up against the
103332         // single triggerCls config. Should rethink this, perhaps something more structured like a list of
103333         // trigger config objects that hold cls, handler, etc.
103334         if (!me.trigger1Cls) {
103335             me.trigger1Cls = me.triggerCls;
103336         }
103337
103338         // Create as many trigger elements as we have trigger<n>Cls configs, but always at least one
103339         for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i < 1; i++) {
103340             triggerConfigs.push({
103341                 cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '),
103342                 role: 'button'
103343             });
103344         }
103345         triggerConfigs[i - 1].cls += ' ' + triggerBaseCls + '-last';
103346
103347         /**
103348          * @property {Ext.Element} triggerWrap
103349          * A reference to the div element wrapping the trigger button(s). Only set after the field has been rendered.
103350          */
103351         me.addChildEls('triggerWrap');
103352
103353         Ext.applyIf(me.subTplData, {
103354             triggerWrapCls: triggerWrapCls,
103355             triggerEl: Ext.DomHelper.markup(triggerConfigs),
103356             clearCls: me.clearCls
103357         });
103358
103359         me.callParent(arguments);
103360
103361         /**
103362          * @property {Ext.CompositeElement} triggerEl
103363          * A composite of all the trigger button elements. Only set after the field has been rendered.
103364          */
103365         me.triggerEl = Ext.select('.' + triggerBaseCls, true, me.triggerWrap.dom);
103366
103367         me.doc = Ext.getDoc();
103368         me.initTrigger();
103369     },
103370
103371     onEnable: function() {
103372         this.callParent();
103373         this.triggerWrap.unmask();
103374     },
103375     
103376     onDisable: function() {
103377         this.callParent();
103378         this.triggerWrap.mask();
103379     },
103380     
103381     afterRender: function() {
103382         this.callParent();
103383         this.updateEditState();
103384         this.triggerEl.unselectable();
103385     },
103386
103387     updateEditState: function() {
103388         var me = this,
103389             inputEl = me.inputEl,
103390             triggerWrap = me.triggerWrap,
103391             noeditCls = Ext.baseCSSPrefix + 'trigger-noedit',
103392             displayed,
103393             readOnly;
103394
103395         if (me.rendered) {
103396             if (me.readOnly) {
103397                 inputEl.addCls(noeditCls);
103398                 readOnly = true;
103399                 displayed = false;
103400             } else {
103401                 if (me.editable) {
103402                     inputEl.removeCls(noeditCls);
103403                     readOnly = false;
103404                 } else {
103405                     inputEl.addCls(noeditCls);
103406                     readOnly = true;
103407                 }
103408                 displayed = !me.hideTrigger;
103409             }
103410
103411             triggerWrap.setDisplayed(displayed);
103412             inputEl.dom.readOnly = readOnly;
103413             me.doComponentLayout();
103414         }
103415     },
103416
103417     /**
103418      * Get the total width of the trigger button area. Only useful after the field has been rendered.
103419      * @return {Number} The trigger width
103420      */
103421     getTriggerWidth: function() {
103422         var me = this,
103423             triggerWrap = me.triggerWrap,
103424             totalTriggerWidth = 0;
103425         if (triggerWrap && !me.hideTrigger && !me.readOnly) {
103426             me.triggerEl.each(function(trigger) {
103427                 totalTriggerWidth += trigger.getWidth();
103428             });
103429             totalTriggerWidth += me.triggerWrap.getFrameWidth('lr');
103430         }
103431         return totalTriggerWidth;
103432     },
103433
103434     setHideTrigger: function(hideTrigger) {
103435         if (hideTrigger != this.hideTrigger) {
103436             this.hideTrigger = hideTrigger;
103437             this.updateEditState();
103438         }
103439     },
103440
103441     /**
103442      * Sets the editable state of this field. This method is the runtime equivalent of setting the 'editable' config
103443      * option at config time.
103444      * @param {Boolean} editable True to allow the user to directly edit the field text. If false is passed, the user
103445      * will only be able to modify the field using the trigger. Will also add a click event to the text field which
103446      * will call the trigger. 
103447      */
103448     setEditable: function(editable) {
103449         if (editable != this.editable) {
103450             this.editable = editable;
103451             this.updateEditState();
103452         }
103453     },
103454
103455     /**
103456      * Sets the read-only state of this field. This method is the runtime equivalent of setting the 'readOnly' config
103457      * option at config time.
103458      * @param {Boolean} readOnly True to prevent the user changing the field and explicitly hide the trigger. Setting
103459      * this to true will superceed settings editable and hideTrigger. Setting this to false will defer back to editable
103460      * and hideTrigger.
103461      */
103462     setReadOnly: function(readOnly) {
103463         if (readOnly != this.readOnly) {
103464             this.readOnly = readOnly;
103465             this.updateEditState();
103466         }
103467     },
103468
103469     // private
103470     initTrigger: function() {
103471         var me = this,
103472             triggerWrap = me.triggerWrap,
103473             triggerEl = me.triggerEl;
103474
103475         if (me.repeatTriggerClick) {
103476             me.triggerRepeater = Ext.create('Ext.util.ClickRepeater', triggerWrap, {
103477                 preventDefault: true,
103478                 handler: function(cr, e) {
103479                     me.onTriggerWrapClick(e);
103480                 }
103481             });
103482         } else {
103483             me.mon(me.triggerWrap, 'click', me.onTriggerWrapClick, me);
103484         }
103485
103486         triggerEl.addClsOnOver(me.triggerBaseCls + '-over');
103487         triggerEl.each(function(el, c, i) {
103488             el.addClsOnOver(me['trigger' + (i + 1) + 'Cls'] + '-over');
103489         });
103490         triggerEl.addClsOnClick(me.triggerBaseCls + '-click');
103491         triggerEl.each(function(el, c, i) {
103492             el.addClsOnClick(me['trigger' + (i + 1) + 'Cls'] + '-click');
103493         });
103494     },
103495
103496     // private
103497     onDestroy: function() {
103498         var me = this;
103499         Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl');
103500         delete me.doc;
103501         me.callParent();
103502     },
103503
103504     // private
103505     onFocus: function() {
103506         var me = this;
103507         me.callParent();
103508         if (!me.mimicing) {
103509             me.bodyEl.addCls(me.wrapFocusCls);
103510             me.mimicing = true;
103511             me.mon(me.doc, 'mousedown', me.mimicBlur, me, {
103512                 delay: 10
103513             });
103514             if (me.monitorTab) {
103515                 me.on('specialkey', me.checkTab, me);
103516             }
103517         }
103518     },
103519
103520     // private
103521     checkTab: function(me, e) {
103522         if (!this.ignoreMonitorTab && e.getKey() == e.TAB) {
103523             this.triggerBlur();
103524         }
103525     },
103526
103527     // private
103528     onBlur: Ext.emptyFn,
103529
103530     // private
103531     mimicBlur: function(e) {
103532         if (!this.isDestroyed && !this.bodyEl.contains(e.target) && this.validateBlur(e)) {
103533             this.triggerBlur();
103534         }
103535     },
103536
103537     // private
103538     triggerBlur: function() {
103539         var me = this;
103540         me.mimicing = false;
103541         me.mun(me.doc, 'mousedown', me.mimicBlur, me);
103542         if (me.monitorTab && me.inputEl) {
103543             me.un('specialkey', me.checkTab, me);
103544         }
103545         Ext.form.field.Trigger.superclass.onBlur.call(me);
103546         if (me.bodyEl) {
103547             me.bodyEl.removeCls(me.wrapFocusCls);
103548         }
103549     },
103550
103551     beforeBlur: Ext.emptyFn,
103552
103553     // private
103554     // This should be overridden by any subclass that needs to check whether or not the field can be blurred.
103555     validateBlur: function(e) {
103556         return true;
103557     },
103558
103559     // private
103560     // process clicks upon triggers.
103561     // determine which trigger index, and dispatch to the appropriate click handler
103562     onTriggerWrapClick: function(e) {
103563         var me = this,
103564             t = e && e.getTarget('.' + Ext.baseCSSPrefix + 'form-trigger', null),
103565             match = t && t.className.match(me.triggerIndexRe),
103566             idx,
103567             triggerClickMethod;
103568
103569         if (match && !me.readOnly) {
103570             idx = parseInt(match[1], 10);
103571             triggerClickMethod = me['onTrigger' + (idx + 1) + 'Click'] || me.onTriggerClick;
103572             if (triggerClickMethod) {
103573                 triggerClickMethod.call(me, e);
103574             }
103575         }
103576     },
103577
103578     /**
103579      * @method onTriggerClick
103580      * @protected
103581      * The function that should handle the trigger's click event. This method does nothing by default until overridden
103582      * by an implementing function. See Ext.form.field.ComboBox and Ext.form.field.Date for sample implementations.
103583      * @param {Ext.EventObject} e
103584      */
103585     onTriggerClick: Ext.emptyFn
103586
103587     /**
103588      * @cfg {Boolean} grow @hide
103589      */
103590     /**
103591      * @cfg {Number} growMin @hide
103592      */
103593     /**
103594      * @cfg {Number} growMax @hide
103595      */
103596 });
103597
103598 /**
103599  * An abstract class for fields that have a single trigger which opens a "picker" popup below the field, e.g. a combobox
103600  * menu list or a date picker. It provides a base implementation for toggling the picker's visibility when the trigger
103601  * is clicked, as well as keyboard navigation and some basic events. Sizing and alignment of the picker can be
103602  * controlled via the {@link #matchFieldWidth} and {@link #pickerAlign}/{@link #pickerOffset} config properties
103603  * respectively.
103604  *
103605  * You would not normally use this class directly, but instead use it as the parent class for a specific picker field
103606  * implementation. Subclasses must implement the {@link #createPicker} method to create a picker component appropriate
103607  * for the field.
103608  */
103609 Ext.define('Ext.form.field.Picker', {
103610     extend: 'Ext.form.field.Trigger',
103611     alias: 'widget.pickerfield',
103612     alternateClassName: 'Ext.form.Picker',
103613     requires: ['Ext.util.KeyNav'],
103614
103615     /**
103616      * @cfg {Boolean} matchFieldWidth
103617      * Whether the picker dropdown's width should be explicitly set to match the width of the field. Defaults to true.
103618      */
103619     matchFieldWidth: true,
103620
103621     /**
103622      * @cfg {String} pickerAlign
103623      * The {@link Ext.Element#alignTo alignment position} with which to align the picker. Defaults to "tl-bl?"
103624      */
103625     pickerAlign: 'tl-bl?',
103626
103627     /**
103628      * @cfg {Number[]} pickerOffset
103629      * An offset [x,y] to use in addition to the {@link #pickerAlign} when positioning the picker.
103630      * Defaults to undefined.
103631      */
103632
103633     /**
103634      * @cfg {String} openCls
103635      * A class to be added to the field's {@link #bodyEl} element when the picker is opened.
103636      * Defaults to 'x-pickerfield-open'.
103637      */
103638     openCls: Ext.baseCSSPrefix + 'pickerfield-open',
103639
103640     /**
103641      * @property {Boolean} isExpanded
103642      * True if the picker is currently expanded, false if not.
103643      */
103644
103645     /**
103646      * @cfg {Boolean} editable
103647      * False to prevent the user from typing text directly into the field; the field can only have its value set via
103648      * selecting a value from the picker. In this state, the picker can also be opened by clicking directly on the input
103649      * field itself.
103650      */
103651     editable: true,
103652
103653
103654     initComponent: function() {
103655         this.callParent();
103656
103657         // Custom events
103658         this.addEvents(
103659             /**
103660              * @event expand
103661              * Fires when the field's picker is expanded.
103662              * @param {Ext.form.field.Picker} field This field instance
103663              */
103664             'expand',
103665             /**
103666              * @event collapse
103667              * Fires when the field's picker is collapsed.
103668              * @param {Ext.form.field.Picker} field This field instance
103669              */
103670             'collapse',
103671             /**
103672              * @event select
103673              * Fires when a value is selected via the picker.
103674              * @param {Ext.form.field.Picker} field This field instance
103675              * @param {Object} value The value that was selected. The exact type of this value is dependent on
103676              * the individual field and picker implementations.
103677              */
103678             'select'
103679         );
103680     },
103681
103682
103683     initEvents: function() {
103684         var me = this;
103685         me.callParent();
103686
103687         // Add handlers for keys to expand/collapse the picker
103688         me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
103689             down: function() {
103690                 if (!me.isExpanded) {
103691                     // Don't call expand() directly as there may be additional processing involved before
103692                     // expanding, e.g. in the case of a ComboBox query.
103693                     me.onTriggerClick();
103694                 }
103695             },
103696             esc: me.collapse,
103697             scope: me,
103698             forceKeyDown: true
103699         });
103700
103701         // Non-editable allows opening the picker by clicking the field
103702         if (!me.editable) {
103703             me.mon(me.inputEl, 'click', me.onTriggerClick, me);
103704         }
103705
103706         // Disable native browser autocomplete
103707         if (Ext.isGecko) {
103708             me.inputEl.dom.setAttribute('autocomplete', 'off');
103709         }
103710     },
103711
103712
103713     /**
103714      * Expands this field's picker dropdown.
103715      */
103716     expand: function() {
103717         var me = this,
103718             bodyEl, picker, collapseIf;
103719
103720         if (me.rendered && !me.isExpanded && !me.isDestroyed) {
103721             bodyEl = me.bodyEl;
103722             picker = me.getPicker();
103723             collapseIf = me.collapseIf;
103724
103725             // show the picker and set isExpanded flag
103726             picker.show();
103727             me.isExpanded = true;
103728             me.alignPicker();
103729             bodyEl.addCls(me.openCls);
103730
103731             // monitor clicking and mousewheel
103732             me.mon(Ext.getDoc(), {
103733                 mousewheel: collapseIf,
103734                 mousedown: collapseIf,
103735                 scope: me
103736             });
103737             Ext.EventManager.onWindowResize(me.alignPicker, me);
103738             me.fireEvent('expand', me);
103739             me.onExpand();
103740         }
103741     },
103742
103743     onExpand: Ext.emptyFn,
103744
103745     /**
103746      * Aligns the picker to the input element
103747      * @protected
103748      */
103749     alignPicker: function() {
103750         var me = this,
103751             picker;
103752
103753         if (me.isExpanded) {
103754             picker = me.getPicker();
103755             if (me.matchFieldWidth) {
103756                 // Auto the height (it will be constrained by min and max width) unless there are no records to display.
103757                 picker.setSize(me.bodyEl.getWidth(), picker.store && picker.store.getCount() ? null : 0);
103758             }
103759             if (picker.isFloating()) {
103760                 me.doAlign();
103761             }
103762         }
103763     },
103764
103765     /**
103766      * Performs the alignment on the picker using the class defaults
103767      * @private
103768      */
103769     doAlign: function(){
103770         var me = this,
103771             picker = me.picker,
103772             aboveSfx = '-above',
103773             isAbove;
103774
103775         me.picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);
103776         // add the {openCls}-above class if the picker was aligned above
103777         // the field due to hitting the bottom of the viewport
103778         isAbove = picker.el.getY() < me.inputEl.getY();
103779         me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
103780         picker[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
103781     },
103782
103783     /**
103784      * Collapses this field's picker dropdown.
103785      */
103786     collapse: function() {
103787         if (this.isExpanded && !this.isDestroyed) {
103788             var me = this,
103789                 openCls = me.openCls,
103790                 picker = me.picker,
103791                 doc = Ext.getDoc(),
103792                 collapseIf = me.collapseIf,
103793                 aboveSfx = '-above';
103794
103795             // hide the picker and set isExpanded flag
103796             picker.hide();
103797             me.isExpanded = false;
103798
103799             // remove the openCls
103800             me.bodyEl.removeCls([openCls, openCls + aboveSfx]);
103801             picker.el.removeCls(picker.baseCls + aboveSfx);
103802
103803             // remove event listeners
103804             doc.un('mousewheel', collapseIf, me);
103805             doc.un('mousedown', collapseIf, me);
103806             Ext.EventManager.removeResizeListener(me.alignPicker, me);
103807             me.fireEvent('collapse', me);
103808             me.onCollapse();
103809         }
103810     },
103811
103812     onCollapse: Ext.emptyFn,
103813
103814
103815     /**
103816      * @private
103817      * Runs on mousewheel and mousedown of doc to check to see if we should collapse the picker
103818      */
103819     collapseIf: function(e) {
103820         var me = this;
103821         if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !e.within(me.picker.el, false, true)) {
103822             me.collapse();
103823         }
103824     },
103825
103826     /**
103827      * Returns a reference to the picker component for this field, creating it if necessary by
103828      * calling {@link #createPicker}.
103829      * @return {Ext.Component} The picker component
103830      */
103831     getPicker: function() {
103832         var me = this;
103833         return me.picker || (me.picker = me.createPicker());
103834     },
103835
103836     /**
103837      * @method
103838      * Creates and returns the component to be used as this field's picker. Must be implemented by subclasses of Picker.
103839      * The current field should also be passed as a configuration option to the picker component as the pickerField
103840      * property.
103841      */
103842     createPicker: Ext.emptyFn,
103843
103844     /**
103845      * Handles the trigger click; by default toggles between expanding and collapsing the picker component.
103846      * @protected
103847      */
103848     onTriggerClick: function() {
103849         var me = this;
103850         if (!me.readOnly && !me.disabled) {
103851             if (me.isExpanded) {
103852                 me.collapse();
103853             } else {
103854                 me.expand();
103855             }
103856             me.inputEl.focus();
103857         }
103858     },
103859
103860     mimicBlur: function(e) {
103861         var me = this,
103862             picker = me.picker;
103863         // ignore mousedown events within the picker element
103864         if (!picker || !e.within(picker.el, false, true)) {
103865             me.callParent(arguments);
103866         }
103867     },
103868
103869     onDestroy : function(){
103870         var me = this,
103871             picker = me.picker;
103872
103873         Ext.EventManager.removeResizeListener(me.alignPicker, me);
103874         Ext.destroy(me.keyNav);
103875         if (picker) {
103876             delete picker.pickerField;
103877             picker.destroy();
103878         }
103879         me.callParent();
103880     }
103881
103882 });
103883
103884
103885 /**
103886  * A field with a pair of up/down spinner buttons. This class is not normally instantiated directly,
103887  * instead it is subclassed and the {@link #onSpinUp} and {@link #onSpinDown} methods are implemented
103888  * to handle when the buttons are clicked. A good example of this is the {@link Ext.form.field.Number}
103889  * field which uses the spinner to increment and decrement the field's value by its
103890  * {@link Ext.form.field.Number#step step} config value.
103891  *
103892  * For example:
103893  *
103894  *     @example
103895  *     Ext.define('Ext.ux.CustomSpinner', {
103896  *         extend: 'Ext.form.field.Spinner',
103897  *         alias: 'widget.customspinner',
103898  *
103899  *         // override onSpinUp (using step isn't neccessary)
103900  *         onSpinUp: function() {
103901  *             var me = this;
103902  *             if (!me.readOnly) {
103903  *                 var val = me.step; // set the default value to the step value
103904  *                 if(me.getValue() !== '') {
103905  *                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
103906  *                 }
103907  *                 me.setValue((val + me.step) + ' Pack');
103908  *             }
103909  *         },
103910  *
103911  *         // override onSpinDown
103912  *         onSpinDown: function() {
103913  *             var val, me = this;
103914  *             if (!me.readOnly) {
103915  *                 if(me.getValue() !== '') {
103916  *                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
103917  *                 }
103918  *                 me.setValue((val - me.step) + ' Pack');
103919  *             }
103920  *         }
103921  *     });
103922  *
103923  *     Ext.create('Ext.form.FormPanel', {
103924  *         title: 'Form with SpinnerField',
103925  *         bodyPadding: 5,
103926  *         width: 350,
103927  *         renderTo: Ext.getBody(),
103928  *         items:[{
103929  *             xtype: 'customspinner',
103930  *             fieldLabel: 'How Much Beer?',
103931  *             step: 6
103932  *         }]
103933  *     });
103934  *
103935  * By default, pressing the up and down arrow keys will also trigger the onSpinUp and onSpinDown methods;
103936  * to prevent this, set `{@link #keyNavEnabled} = false`.
103937  */
103938 Ext.define('Ext.form.field.Spinner', {
103939     extend: 'Ext.form.field.Trigger',
103940     alias: 'widget.spinnerfield',
103941     alternateClassName: 'Ext.form.Spinner',
103942     requires: ['Ext.util.KeyNav'],
103943
103944     trigger1Cls: Ext.baseCSSPrefix + 'form-spinner-up',
103945     trigger2Cls: Ext.baseCSSPrefix + 'form-spinner-down',
103946
103947     /**
103948      * @cfg {Boolean} spinUpEnabled
103949      * Specifies whether the up spinner button is enabled. Defaults to true. To change this after the component is
103950      * created, use the {@link #setSpinUpEnabled} method.
103951      */
103952     spinUpEnabled: true,
103953
103954     /**
103955      * @cfg {Boolean} spinDownEnabled
103956      * Specifies whether the down spinner button is enabled. Defaults to true. To change this after the component is
103957      * created, use the {@link #setSpinDownEnabled} method.
103958      */
103959     spinDownEnabled: true,
103960
103961     /**
103962      * @cfg {Boolean} keyNavEnabled
103963      * Specifies whether the up and down arrow keys should trigger spinning up and down. Defaults to true.
103964      */
103965     keyNavEnabled: true,
103966
103967     /**
103968      * @cfg {Boolean} mouseWheelEnabled
103969      * Specifies whether the mouse wheel should trigger spinning up and down while the field has focus.
103970      * Defaults to true.
103971      */
103972     mouseWheelEnabled: true,
103973
103974     /**
103975      * @cfg {Boolean} repeatTriggerClick
103976      * Whether a {@link Ext.util.ClickRepeater click repeater} should be attached to the spinner buttons.
103977      * Defaults to true.
103978      */
103979     repeatTriggerClick: true,
103980
103981     /**
103982      * @method
103983      * @protected
103984      * This method is called when the spinner up button is clicked, or when the up arrow key is pressed if
103985      * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
103986      */
103987     onSpinUp: Ext.emptyFn,
103988
103989     /**
103990      * @method
103991      * @protected
103992      * This method is called when the spinner down button is clicked, or when the down arrow key is pressed if
103993      * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
103994      */
103995     onSpinDown: Ext.emptyFn,
103996
103997     initComponent: function() {
103998         this.callParent();
103999
104000         this.addEvents(
104001             /**
104002              * @event spin
104003              * Fires when the spinner is made to spin up or down.
104004              * @param {Ext.form.field.Spinner} this
104005              * @param {String} direction Either 'up' if spinning up, or 'down' if spinning down.
104006              */
104007             'spin',
104008
104009             /**
104010              * @event spinup
104011              * Fires when the spinner is made to spin up.
104012              * @param {Ext.form.field.Spinner} this
104013              */
104014             'spinup',
104015
104016             /**
104017              * @event spindown
104018              * Fires when the spinner is made to spin down.
104019              * @param {Ext.form.field.Spinner} this
104020              */
104021             'spindown'
104022         );
104023     },
104024
104025     /**
104026      * @private
104027      * Override.
104028      */
104029     onRender: function() {
104030         var me = this,
104031             triggers;
104032
104033         me.callParent(arguments);
104034         triggers = me.triggerEl;
104035
104036         /**
104037          * @property {Ext.Element} spinUpEl
104038          * The spinner up button element
104039          */
104040         me.spinUpEl = triggers.item(0);
104041         /**
104042          * @property {Ext.Element} spinDownEl
104043          * The spinner down button element
104044          */
104045         me.spinDownEl = triggers.item(1);
104046
104047         // Set initial enabled/disabled states
104048         me.setSpinUpEnabled(me.spinUpEnabled);
104049         me.setSpinDownEnabled(me.spinDownEnabled);
104050
104051         // Init up/down arrow keys
104052         if (me.keyNavEnabled) {
104053             me.spinnerKeyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
104054                 scope: me,
104055                 up: me.spinUp,
104056                 down: me.spinDown
104057             });
104058         }
104059
104060         // Init mouse wheel
104061         if (me.mouseWheelEnabled) {
104062             me.mon(me.bodyEl, 'mousewheel', me.onMouseWheel, me);
104063         }
104064     },
104065
104066     /**
104067      * @private
104068      * Override. Since the triggers are stacked, only measure the width of one of them.
104069      */
104070     getTriggerWidth: function() {
104071         return this.hideTrigger || this.readOnly ? 0 : this.spinUpEl.getWidth() + this.triggerWrap.getFrameWidth('lr');
104072     },
104073
104074     /**
104075      * @private
104076      * Handles the spinner up button clicks.
104077      */
104078     onTrigger1Click: function() {
104079         this.spinUp();
104080     },
104081
104082     /**
104083      * @private
104084      * Handles the spinner down button clicks.
104085      */
104086     onTrigger2Click: function() {
104087         this.spinDown();
104088     },
104089
104090     /**
104091      * Triggers the spinner to step up; fires the {@link #spin} and {@link #spinup} events and calls the
104092      * {@link #onSpinUp} method. Does nothing if the field is {@link #disabled} or if {@link #spinUpEnabled}
104093      * is false.
104094      */
104095     spinUp: function() {
104096         var me = this;
104097         if (me.spinUpEnabled && !me.disabled) {
104098             me.fireEvent('spin', me, 'up');
104099             me.fireEvent('spinup', me);
104100             me.onSpinUp();
104101         }
104102     },
104103
104104     /**
104105      * Triggers the spinner to step down; fires the {@link #spin} and {@link #spindown} events and calls the
104106      * {@link #onSpinDown} method. Does nothing if the field is {@link #disabled} or if {@link #spinDownEnabled}
104107      * is false.
104108      */
104109     spinDown: function() {
104110         var me = this;
104111         if (me.spinDownEnabled && !me.disabled) {
104112             me.fireEvent('spin', me, 'down');
104113             me.fireEvent('spindown', me);
104114             me.onSpinDown();
104115         }
104116     },
104117
104118     /**
104119      * Sets whether the spinner up button is enabled.
104120      * @param {Boolean} enabled true to enable the button, false to disable it.
104121      */
104122     setSpinUpEnabled: function(enabled) {
104123         var me = this,
104124             wasEnabled = me.spinUpEnabled;
104125         me.spinUpEnabled = enabled;
104126         if (wasEnabled !== enabled && me.rendered) {
104127             me.spinUpEl[enabled ? 'removeCls' : 'addCls'](me.trigger1Cls + '-disabled');
104128         }
104129     },
104130
104131     /**
104132      * Sets whether the spinner down button is enabled.
104133      * @param {Boolean} enabled true to enable the button, false to disable it.
104134      */
104135     setSpinDownEnabled: function(enabled) {
104136         var me = this,
104137             wasEnabled = me.spinDownEnabled;
104138         me.spinDownEnabled = enabled;
104139         if (wasEnabled !== enabled && me.rendered) {
104140             me.spinDownEl[enabled ? 'removeCls' : 'addCls'](me.trigger2Cls + '-disabled');
104141         }
104142     },
104143
104144     /**
104145      * @private
104146      * Handles mousewheel events on the field
104147      */
104148     onMouseWheel: function(e) {
104149         var me = this,
104150             delta;
104151         if (me.hasFocus) {
104152             delta = e.getWheelDelta();
104153             if (delta > 0) {
104154                 me.spinUp();
104155             }
104156             else if (delta < 0) {
104157                 me.spinDown();
104158             }
104159             e.stopEvent();
104160         }
104161     },
104162
104163     onDestroy: function() {
104164         Ext.destroyMembers(this, 'spinnerKeyNav', 'spinUpEl', 'spinDownEl');
104165         this.callParent();
104166     }
104167
104168 });
104169 /**
104170  * @docauthor Jason Johnston <jason@sencha.com>
104171  *
104172  * A numeric text field that provides automatic keystroke filtering to disallow non-numeric characters,
104173  * and numeric validation to limit the value to a range of valid numbers. The range of acceptable number
104174  * values can be controlled by setting the {@link #minValue} and {@link #maxValue} configs, and fractional
104175  * decimals can be disallowed by setting {@link #allowDecimals} to `false`.
104176  *
104177  * By default, the number field is also rendered with a set of up/down spinner buttons and has
104178  * up/down arrow key and mouse wheel event listeners attached for incrementing/decrementing the value by the
104179  * {@link #step} value. To hide the spinner buttons set `{@link #hideTrigger hideTrigger}:true`; to disable
104180  * the arrow key and mouse wheel handlers set `{@link #keyNavEnabled keyNavEnabled}:false` and
104181  * `{@link #mouseWheelEnabled mouseWheelEnabled}:false`. See the example below.
104182  *
104183  * # Example usage
104184  *
104185  *     @example
104186  *     Ext.create('Ext.form.Panel', {
104187  *         title: 'On The Wall',
104188  *         width: 300,
104189  *         bodyPadding: 10,
104190  *         renderTo: Ext.getBody(),
104191  *         items: [{
104192  *             xtype: 'numberfield',
104193  *             anchor: '100%',
104194  *             name: 'bottles',
104195  *             fieldLabel: 'Bottles of Beer',
104196  *             value: 99,
104197  *             maxValue: 99,
104198  *             minValue: 0
104199  *         }],
104200  *         buttons: [{
104201  *             text: 'Take one down, pass it around',
104202  *             handler: function() {
104203  *                 this.up('form').down('[name=bottles]').spinDown();
104204  *             }
104205  *         }]
104206  *     });
104207  *
104208  * # Removing UI Enhancements
104209  *
104210  *     @example
104211  *     Ext.create('Ext.form.Panel', {
104212  *         title: 'Personal Info',
104213  *         width: 300,
104214  *         bodyPadding: 10,
104215  *         renderTo: Ext.getBody(),
104216  *         items: [{
104217  *             xtype: 'numberfield',
104218  *             anchor: '100%',
104219  *             name: 'age',
104220  *             fieldLabel: 'Age',
104221  *             minValue: 0, //prevents negative numbers
104222  *
104223  *             // Remove spinner buttons, and arrow key and mouse wheel listeners
104224  *             hideTrigger: true,
104225  *             keyNavEnabled: false,
104226  *             mouseWheelEnabled: false
104227  *         }]
104228  *     });
104229  *
104230  * # Using Step
104231  *
104232  *     @example
104233  *     Ext.create('Ext.form.Panel', {
104234  *         renderTo: Ext.getBody(),
104235  *         title: 'Step',
104236  *         width: 300,
104237  *         bodyPadding: 10,
104238  *         items: [{
104239  *             xtype: 'numberfield',
104240  *             anchor: '100%',
104241  *             name: 'evens',
104242  *             fieldLabel: 'Even Numbers',
104243  *
104244  *             // Set step so it skips every other number
104245  *             step: 2,
104246  *             value: 0,
104247  *
104248  *             // Add change handler to force user-entered numbers to evens
104249  *             listeners: {
104250  *                 change: function(field, value) {
104251  *                     value = parseInt(value, 10);
104252  *                     field.setValue(value + value % 2);
104253  *                 }
104254  *             }
104255  *         }]
104256  *     });
104257  */
104258 Ext.define('Ext.form.field.Number', {
104259     extend:'Ext.form.field.Spinner',
104260     alias: 'widget.numberfield',
104261     alternateClassName: ['Ext.form.NumberField', 'Ext.form.Number'],
104262
104263     /**
104264      * @cfg {RegExp} stripCharsRe @hide
104265      */
104266     /**
104267      * @cfg {RegExp} maskRe @hide
104268      */
104269
104270     /**
104271      * @cfg {Boolean} allowDecimals
104272      * False to disallow decimal values
104273      */
104274     allowDecimals : true,
104275
104276     /**
104277      * @cfg {String} decimalSeparator
104278      * Character(s) to allow as the decimal separator
104279      */
104280     decimalSeparator : '.',
104281
104282     /**
104283      * @cfg {Number} decimalPrecision
104284      * The maximum precision to display after the decimal separator
104285      */
104286     decimalPrecision : 2,
104287
104288     /**
104289      * @cfg {Number} minValue
104290      * The minimum allowed value (defaults to Number.NEGATIVE_INFINITY). Will be used by the field's validation logic,
104291      * and for {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the down spinner button}.
104292      */
104293     minValue: Number.NEGATIVE_INFINITY,
104294
104295     /**
104296      * @cfg {Number} maxValue
104297      * The maximum allowed value (defaults to Number.MAX_VALUE). Will be used by the field's validation logic, and for
104298      * {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the up spinner button}.
104299      */
104300     maxValue: Number.MAX_VALUE,
104301
104302     /**
104303      * @cfg {Number} step
104304      * Specifies a numeric interval by which the field's value will be incremented or decremented when the user invokes
104305      * the spinner.
104306      */
104307     step: 1,
104308
104309     /**
104310      * @cfg {String} minText
104311      * Error text to display if the minimum value validation fails.
104312      */
104313     minText : 'The minimum value for this field is {0}',
104314
104315     /**
104316      * @cfg {String} maxText
104317      * Error text to display if the maximum value validation fails.
104318      */
104319     maxText : 'The maximum value for this field is {0}',
104320
104321     /**
104322      * @cfg {String} nanText
104323      * Error text to display if the value is not a valid number. For example, this can happen if a valid character like
104324      * '.' or '-' is left in the field with no number.
104325      */
104326     nanText : '{0} is not a valid number',
104327
104328     /**
104329      * @cfg {String} negativeText
104330      * Error text to display if the value is negative and {@link #minValue} is set to 0. This is used instead of the
104331      * {@link #minText} in that circumstance only.
104332      */
104333     negativeText : 'The value cannot be negative',
104334
104335     /**
104336      * @cfg {String} baseChars
104337      * The base set of characters to evaluate as valid numbers.
104338      */
104339     baseChars : '0123456789',
104340
104341     /**
104342      * @cfg {Boolean} autoStripChars
104343      * True to automatically strip not allowed characters from the field.
104344      */
104345     autoStripChars: false,
104346
104347     initComponent: function() {
104348         var me = this,
104349             allowed;
104350
104351         me.callParent();
104352
104353         me.setMinValue(me.minValue);
104354         me.setMaxValue(me.maxValue);
104355
104356         // Build regexes for masking and stripping based on the configured options
104357         if (me.disableKeyFilter !== true) {
104358             allowed = me.baseChars + '';
104359             if (me.allowDecimals) {
104360                 allowed += me.decimalSeparator;
104361             }
104362             if (me.minValue < 0) {
104363                 allowed += '-';
104364             }
104365             allowed = Ext.String.escapeRegex(allowed);
104366             me.maskRe = new RegExp('[' + allowed + ']');
104367             if (me.autoStripChars) {
104368                 me.stripCharsRe = new RegExp('[^' + allowed + ']', 'gi');
104369             }
104370         }
104371     },
104372
104373     /**
104374      * Runs all of Number's validations and returns an array of any errors. Note that this first runs Text's
104375      * validations, so the returned array is an amalgamation of all field errors. The additional validations run test
104376      * that the value is a number, and that it is within the configured min and max values.
104377      * @param {Object} [value] The value to get errors for (defaults to the current field value)
104378      * @return {String[]} All validation errors for this field
104379      */
104380     getErrors: function(value) {
104381         var me = this,
104382             errors = me.callParent(arguments),
104383             format = Ext.String.format,
104384             num;
104385
104386         value = Ext.isDefined(value) ? value : this.processRawValue(this.getRawValue());
104387
104388         if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
104389              return errors;
104390         }
104391
104392         value = String(value).replace(me.decimalSeparator, '.');
104393
104394         if(isNaN(value)){
104395             errors.push(format(me.nanText, value));
104396         }
104397
104398         num = me.parseValue(value);
104399
104400         if (me.minValue === 0 && num < 0) {
104401             errors.push(this.negativeText);
104402         }
104403         else if (num < me.minValue) {
104404             errors.push(format(me.minText, me.minValue));
104405         }
104406
104407         if (num > me.maxValue) {
104408             errors.push(format(me.maxText, me.maxValue));
104409         }
104410
104411
104412         return errors;
104413     },
104414
104415     rawToValue: function(rawValue) {
104416         var value = this.fixPrecision(this.parseValue(rawValue));
104417         if (value === null) {
104418             value = rawValue || null;
104419         }
104420         return  value;
104421     },
104422
104423     valueToRaw: function(value) {
104424         var me = this,
104425             decimalSeparator = me.decimalSeparator;
104426         value = me.parseValue(value);
104427         value = me.fixPrecision(value);
104428         value = Ext.isNumber(value) ? value : parseFloat(String(value).replace(decimalSeparator, '.'));
104429         value = isNaN(value) ? '' : String(value).replace('.', decimalSeparator);
104430         return value;
104431     },
104432
104433     onChange: function() {
104434         var me = this,
104435             value = me.getValue(),
104436             valueIsNull = value === null;
104437
104438         me.callParent(arguments);
104439
104440         // Update the spinner buttons
104441         me.setSpinUpEnabled(valueIsNull || value < me.maxValue);
104442         me.setSpinDownEnabled(valueIsNull || value > me.minValue);
104443     },
104444
104445     /**
104446      * Replaces any existing {@link #minValue} with the new value.
104447      * @param {Number} value The minimum value
104448      */
104449     setMinValue : function(value) {
104450         this.minValue = Ext.Number.from(value, Number.NEGATIVE_INFINITY);
104451     },
104452
104453     /**
104454      * Replaces any existing {@link #maxValue} with the new value.
104455      * @param {Number} value The maximum value
104456      */
104457     setMaxValue: function(value) {
104458         this.maxValue = Ext.Number.from(value, Number.MAX_VALUE);
104459     },
104460
104461     // private
104462     parseValue : function(value) {
104463         value = parseFloat(String(value).replace(this.decimalSeparator, '.'));
104464         return isNaN(value) ? null : value;
104465     },
104466
104467     /**
104468      * @private
104469      */
104470     fixPrecision : function(value) {
104471         var me = this,
104472             nan = isNaN(value),
104473             precision = me.decimalPrecision;
104474
104475         if (nan || !value) {
104476             return nan ? '' : value;
104477         } else if (!me.allowDecimals || precision <= 0) {
104478             precision = 0;
104479         }
104480
104481         return parseFloat(Ext.Number.toFixed(parseFloat(value), precision));
104482     },
104483
104484     beforeBlur : function() {
104485         var me = this,
104486             v = me.parseValue(me.getRawValue());
104487
104488         if (!Ext.isEmpty(v)) {
104489             me.setValue(v);
104490         }
104491     },
104492
104493     onSpinUp: function() {
104494         var me = this;
104495         if (!me.readOnly) {
104496             me.setValue(Ext.Number.constrain(me.getValue() + me.step, me.minValue, me.maxValue));
104497         }
104498     },
104499
104500     onSpinDown: function() {
104501         var me = this;
104502         if (!me.readOnly) {
104503             me.setValue(Ext.Number.constrain(me.getValue() - me.step, me.minValue, me.maxValue));
104504         }
104505     }
104506 });
104507
104508 /**
104509  * As the number of records increases, the time required for the browser to render them increases. Paging is used to
104510  * reduce the amount of data exchanged with the client. Note: if there are more records/rows than can be viewed in the
104511  * available screen area, vertical scrollbars will be added.
104512  *
104513  * Paging is typically handled on the server side (see exception below). The client sends parameters to the server side,
104514  * which the server needs to interpret and then respond with the appropriate data.
104515  *
104516  * Ext.toolbar.Paging is a specialized toolbar that is bound to a {@link Ext.data.Store} and provides automatic
104517  * paging control. This Component {@link Ext.data.Store#load load}s blocks of data into the {@link #store} by passing
104518  * parameters used for paging criteria.
104519  *
104520  * {@img Ext.toolbar.Paging/Ext.toolbar.Paging.png Ext.toolbar.Paging component}
104521  *
104522  * Paging Toolbar is typically used as one of the Grid's toolbars:
104523  *
104524  *     @example
104525  *     var itemsPerPage = 2;   // set the number of items you want per page
104526  *
104527  *     var store = Ext.create('Ext.data.Store', {
104528  *         id:'simpsonsStore',
104529  *         autoLoad: false,
104530  *         fields:['name', 'email', 'phone'],
104531  *         pageSize: itemsPerPage, // items per page
104532  *         proxy: {
104533  *             type: 'ajax',
104534  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
104535  *             reader: {
104536  *                 type: 'json',
104537  *                 root: 'items',
104538  *                 totalProperty: 'total'
104539  *             }
104540  *         }
104541  *     });
104542  *
104543  *     // specify segment of data you want to load using params
104544  *     store.load({
104545  *         params:{
104546  *             start:0,
104547  *             limit: itemsPerPage
104548  *         }
104549  *     });
104550  *
104551  *     Ext.create('Ext.grid.Panel', {
104552  *         title: 'Simpsons',
104553  *         store: store,
104554  *         columns: [
104555  *             { header: 'Name',  dataIndex: 'name' },
104556  *             { header: 'Email', dataIndex: 'email', flex: 1 },
104557  *             { header: 'Phone', dataIndex: 'phone' }
104558  *         ],
104559  *         width: 400,
104560  *         height: 125,
104561  *         dockedItems: [{
104562  *             xtype: 'pagingtoolbar',
104563  *             store: store,   // same store GridPanel is using
104564  *             dock: 'bottom',
104565  *             displayInfo: true
104566  *         }],
104567  *         renderTo: Ext.getBody()
104568  *     });
104569  *
104570  * To use paging, pass the paging requirements to the server when the store is first loaded.
104571  *
104572  *     store.load({
104573  *         params: {
104574  *             // specify params for the first page load if using paging
104575  *             start: 0,
104576  *             limit: myPageSize,
104577  *             // other params
104578  *             foo:   'bar'
104579  *         }
104580  *     });
104581  *
104582  * If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration:
104583  *
104584  *     var myStore = Ext.create('Ext.data.Store', {
104585  *         {@link Ext.data.Store#autoLoad autoLoad}: {start: 0, limit: 25},
104586  *         ...
104587  *     });
104588  *
104589  * The packet sent back from the server would have this form:
104590  *
104591  *     {
104592  *         "success": true,
104593  *         "results": 2000,
104594  *         "rows": [ // ***Note:** this must be an Array
104595  *             { "id":  1, "name": "Bill", "occupation": "Gardener" },
104596  *             { "id":  2, "name":  "Ben", "occupation": "Horticulturalist" },
104597  *             ...
104598  *             { "id": 25, "name":  "Sue", "occupation": "Botanist" }
104599  *         ]
104600  *     }
104601  *
104602  * ## Paging with Local Data
104603  *
104604  * Paging can also be accomplished with local data using extensions:
104605  *
104606  *   - [Ext.ux.data.PagingStore][1]
104607  *   - Paging Memory Proxy (examples/ux/PagingMemoryProxy.js)
104608  *
104609  *    [1]: http://sencha.com/forum/showthread.php?t=71532
104610  */
104611 Ext.define('Ext.toolbar.Paging', {
104612     extend: 'Ext.toolbar.Toolbar',
104613     alias: 'widget.pagingtoolbar',
104614     alternateClassName: 'Ext.PagingToolbar',
104615     requires: ['Ext.toolbar.TextItem', 'Ext.form.field.Number'],
104616     /**
104617      * @cfg {Ext.data.Store} store (required)
104618      * The {@link Ext.data.Store} the paging toolbar should use as its data source.
104619      */
104620
104621     /**
104622      * @cfg {Boolean} displayInfo
104623      * true to display the displayMsg
104624      */
104625     displayInfo: false,
104626
104627     /**
104628      * @cfg {Boolean} prependButtons
104629      * true to insert any configured items _before_ the paging buttons.
104630      */
104631     prependButtons: false,
104632
104633     /**
104634      * @cfg {String} displayMsg
104635      * The paging status message to display. Note that this string is
104636      * formatted using the braced numbers {0}-{2} as tokens that are replaced by the values for start, end and total
104637      * respectively. These tokens should be preserved when overriding this string if showing those values is desired.
104638      */
104639     displayMsg : 'Displaying {0} - {1} of {2}',
104640
104641     /**
104642      * @cfg {String} emptyMsg
104643      * The message to display when no records are found.
104644      */
104645     emptyMsg : 'No data to display',
104646
104647     /**
104648      * @cfg {String} beforePageText
104649      * The text displayed before the input item.
104650      */
104651     beforePageText : 'Page',
104652
104653     /**
104654      * @cfg {String} afterPageText
104655      * Customizable piece of the default paging text. Note that this string is formatted using
104656      * {0} as a token that is replaced by the number of total pages. This token should be preserved when overriding this
104657      * string if showing the total page count is desired.
104658      */
104659     afterPageText : 'of {0}',
104660
104661     /**
104662      * @cfg {String} firstText
104663      * The quicktip text displayed for the first page button.
104664      * **Note**: quick tips must be initialized for the quicktip to show.
104665      */
104666     firstText : 'First Page',
104667
104668     /**
104669      * @cfg {String} prevText
104670      * The quicktip text displayed for the previous page button.
104671      * **Note**: quick tips must be initialized for the quicktip to show.
104672      */
104673     prevText : 'Previous Page',
104674
104675     /**
104676      * @cfg {String} nextText
104677      * The quicktip text displayed for the next page button.
104678      * **Note**: quick tips must be initialized for the quicktip to show.
104679      */
104680     nextText : 'Next Page',
104681
104682     /**
104683      * @cfg {String} lastText
104684      * The quicktip text displayed for the last page button.
104685      * **Note**: quick tips must be initialized for the quicktip to show.
104686      */
104687     lastText : 'Last Page',
104688
104689     /**
104690      * @cfg {String} refreshText
104691      * The quicktip text displayed for the Refresh button.
104692      * **Note**: quick tips must be initialized for the quicktip to show.
104693      */
104694     refreshText : 'Refresh',
104695
104696     /**
104697      * @cfg {Number} inputItemWidth
104698      * The width in pixels of the input field used to display and change the current page number.
104699      */
104700     inputItemWidth : 30,
104701
104702     /**
104703      * Gets the standard paging items in the toolbar
104704      * @private
104705      */
104706     getPagingItems: function() {
104707         var me = this;
104708
104709         return [{
104710             itemId: 'first',
104711             tooltip: me.firstText,
104712             overflowText: me.firstText,
104713             iconCls: Ext.baseCSSPrefix + 'tbar-page-first',
104714             disabled: true,
104715             handler: me.moveFirst,
104716             scope: me
104717         },{
104718             itemId: 'prev',
104719             tooltip: me.prevText,
104720             overflowText: me.prevText,
104721             iconCls: Ext.baseCSSPrefix + 'tbar-page-prev',
104722             disabled: true,
104723             handler: me.movePrevious,
104724             scope: me
104725         },
104726         '-',
104727         me.beforePageText,
104728         {
104729             xtype: 'numberfield',
104730             itemId: 'inputItem',
104731             name: 'inputItem',
104732             cls: Ext.baseCSSPrefix + 'tbar-page-number',
104733             allowDecimals: false,
104734             minValue: 1,
104735             hideTrigger: true,
104736             enableKeyEvents: true,
104737             selectOnFocus: true,
104738             submitValue: false,
104739             width: me.inputItemWidth,
104740             margins: '-1 2 3 2',
104741             listeners: {
104742                 scope: me,
104743                 keydown: me.onPagingKeyDown,
104744                 blur: me.onPagingBlur
104745             }
104746         },{
104747             xtype: 'tbtext',
104748             itemId: 'afterTextItem',
104749             text: Ext.String.format(me.afterPageText, 1)
104750         },
104751         '-',
104752         {
104753             itemId: 'next',
104754             tooltip: me.nextText,
104755             overflowText: me.nextText,
104756             iconCls: Ext.baseCSSPrefix + 'tbar-page-next',
104757             disabled: true,
104758             handler: me.moveNext,
104759             scope: me
104760         },{
104761             itemId: 'last',
104762             tooltip: me.lastText,
104763             overflowText: me.lastText,
104764             iconCls: Ext.baseCSSPrefix + 'tbar-page-last',
104765             disabled: true,
104766             handler: me.moveLast,
104767             scope: me
104768         },
104769         '-',
104770         {
104771             itemId: 'refresh',
104772             tooltip: me.refreshText,
104773             overflowText: me.refreshText,
104774             iconCls: Ext.baseCSSPrefix + 'tbar-loading',
104775             handler: me.doRefresh,
104776             scope: me
104777         }];
104778     },
104779
104780     initComponent : function(){
104781         var me = this,
104782             pagingItems = me.getPagingItems(),
104783             userItems   = me.items || me.buttons || [];
104784
104785         if (me.prependButtons) {
104786             me.items = userItems.concat(pagingItems);
104787         } else {
104788             me.items = pagingItems.concat(userItems);
104789         }
104790         delete me.buttons;
104791
104792         if (me.displayInfo) {
104793             me.items.push('->');
104794             me.items.push({xtype: 'tbtext', itemId: 'displayItem'});
104795         }
104796
104797         me.callParent();
104798
104799         me.addEvents(
104800             /**
104801              * @event change
104802              * Fires after the active page has been changed.
104803              * @param {Ext.toolbar.Paging} this
104804              * @param {Object} pageData An object that has these properties:
104805              *
104806              * - `total` : Number
104807              *
104808              *   The total number of records in the dataset as returned by the server
104809              *
104810              * - `currentPage` : Number
104811              *
104812              *   The current page number
104813              *
104814              * - `pageCount` : Number
104815              *
104816              *   The total number of pages (calculated from the total number of records in the dataset as returned by the
104817              *   server and the current {@link Ext.data.Store#pageSize pageSize})
104818              *
104819              * - `toRecord` : Number
104820              *
104821              *   The starting record index for the current page
104822              *
104823              * - `fromRecord` : Number
104824              *
104825              *   The ending record index for the current page
104826              */
104827             'change',
104828
104829             /**
104830              * @event beforechange
104831              * Fires just before the active page is changed. Return false to prevent the active page from being changed.
104832              * @param {Ext.toolbar.Paging} this
104833              * @param {Number} page The page number that will be loaded on change
104834              */
104835             'beforechange'
104836         );
104837         me.on('afterlayout', me.onLoad, me, {single: true});
104838
104839         me.bindStore(me.store || 'ext-empty-store', true);
104840     },
104841     // private
104842     updateInfo : function(){
104843         var me = this,
104844             displayItem = me.child('#displayItem'),
104845             store = me.store,
104846             pageData = me.getPageData(),
104847             count, msg;
104848
104849         if (displayItem) {
104850             count = store.getCount();
104851             if (count === 0) {
104852                 msg = me.emptyMsg;
104853             } else {
104854                 msg = Ext.String.format(
104855                     me.displayMsg,
104856                     pageData.fromRecord,
104857                     pageData.toRecord,
104858                     pageData.total
104859                 );
104860             }
104861             displayItem.setText(msg);
104862             me.doComponentLayout();
104863         }
104864     },
104865
104866     // private
104867     onLoad : function(){
104868         var me = this,
104869             pageData,
104870             currPage,
104871             pageCount,
104872             afterText;
104873
104874         if (!me.rendered) {
104875             return;
104876         }
104877
104878         pageData = me.getPageData();
104879         currPage = pageData.currentPage;
104880         pageCount = pageData.pageCount;
104881         afterText = Ext.String.format(me.afterPageText, isNaN(pageCount) ? 1 : pageCount);
104882
104883         me.child('#afterTextItem').setText(afterText);
104884         me.child('#inputItem').setValue(currPage);
104885         me.child('#first').setDisabled(currPage === 1);
104886         me.child('#prev').setDisabled(currPage === 1);
104887         me.child('#next').setDisabled(currPage === pageCount);
104888         me.child('#last').setDisabled(currPage === pageCount);
104889         me.child('#refresh').enable();
104890         me.updateInfo();
104891         me.fireEvent('change', me, pageData);
104892     },
104893
104894     // private
104895     getPageData : function(){
104896         var store = this.store,
104897             totalCount = store.getTotalCount();
104898
104899         return {
104900             total : totalCount,
104901             currentPage : store.currentPage,
104902             pageCount: Math.ceil(totalCount / store.pageSize),
104903             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
104904             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
104905
104906         };
104907     },
104908
104909     // private
104910     onLoadError : function(){
104911         if (!this.rendered) {
104912             return;
104913         }
104914         this.child('#refresh').enable();
104915     },
104916
104917     // private
104918     readPageFromInput : function(pageData){
104919         var v = this.child('#inputItem').getValue(),
104920             pageNum = parseInt(v, 10);
104921
104922         if (!v || isNaN(pageNum)) {
104923             this.child('#inputItem').setValue(pageData.currentPage);
104924             return false;
104925         }
104926         return pageNum;
104927     },
104928
104929     onPagingFocus : function(){
104930         this.child('#inputItem').select();
104931     },
104932
104933     //private
104934     onPagingBlur : function(e){
104935         var curPage = this.getPageData().currentPage;
104936         this.child('#inputItem').setValue(curPage);
104937     },
104938
104939     // private
104940     onPagingKeyDown : function(field, e){
104941         var me = this,
104942             k = e.getKey(),
104943             pageData = me.getPageData(),
104944             increment = e.shiftKey ? 10 : 1,
104945             pageNum;
104946
104947         if (k == e.RETURN) {
104948             e.stopEvent();
104949             pageNum = me.readPageFromInput(pageData);
104950             if (pageNum !== false) {
104951                 pageNum = Math.min(Math.max(1, pageNum), pageData.pageCount);
104952                 if(me.fireEvent('beforechange', me, pageNum) !== false){
104953                     me.store.loadPage(pageNum);
104954                 }
104955             }
104956         } else if (k == e.HOME || k == e.END) {
104957             e.stopEvent();
104958             pageNum = k == e.HOME ? 1 : pageData.pageCount;
104959             field.setValue(pageNum);
104960         } else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN) {
104961             e.stopEvent();
104962             pageNum = me.readPageFromInput(pageData);
104963             if (pageNum) {
104964                 if (k == e.DOWN || k == e.PAGEDOWN) {
104965                     increment *= -1;
104966                 }
104967                 pageNum += increment;
104968                 if (pageNum >= 1 && pageNum <= pageData.pages) {
104969                     field.setValue(pageNum);
104970                 }
104971             }
104972         }
104973     },
104974
104975     // private
104976     beforeLoad : function(){
104977         if(this.rendered && this.refresh){
104978             this.refresh.disable();
104979         }
104980     },
104981
104982     // private
104983     doLoad : function(start){
104984         if(this.fireEvent('beforechange', this, o) !== false){
104985             this.store.load();
104986         }
104987     },
104988
104989     /**
104990      * Move to the first page, has the same effect as clicking the 'first' button.
104991      */
104992     moveFirst : function(){
104993         if (this.fireEvent('beforechange', this, 1) !== false){
104994             this.store.loadPage(1);
104995         }
104996     },
104997
104998     /**
104999      * Move to the previous page, has the same effect as clicking the 'previous' button.
105000      */
105001     movePrevious : function(){
105002         var me = this,
105003             prev = me.store.currentPage - 1;
105004
105005         if (prev > 0) {
105006             if (me.fireEvent('beforechange', me, prev) !== false) {
105007                 me.store.previousPage();
105008             }
105009         }
105010     },
105011
105012     /**
105013      * Move to the next page, has the same effect as clicking the 'next' button.
105014      */
105015     moveNext : function(){
105016         var me = this,
105017             total = me.getPageData().pageCount,
105018             next = me.store.currentPage + 1;
105019
105020         if (next <= total) {
105021             if (me.fireEvent('beforechange', me, next) !== false) {
105022                 me.store.nextPage();
105023             }
105024         }
105025     },
105026
105027     /**
105028      * Move to the last page, has the same effect as clicking the 'last' button.
105029      */
105030     moveLast : function(){
105031         var me = this,
105032             last = me.getPageData().pageCount;
105033
105034         if (me.fireEvent('beforechange', me, last) !== false) {
105035             me.store.loadPage(last);
105036         }
105037     },
105038
105039     /**
105040      * Refresh the current page, has the same effect as clicking the 'refresh' button.
105041      */
105042     doRefresh : function(){
105043         var me = this,
105044             current = me.store.currentPage;
105045
105046         if (me.fireEvent('beforechange', me, current) !== false) {
105047             me.store.loadPage(current);
105048         }
105049     },
105050
105051     /**
105052      * Binds the paging toolbar to the specified {@link Ext.data.Store}
105053      * @param {Ext.data.Store} store The store to bind to this toolbar
105054      * @param {Boolean} initial (Optional) true to not remove listeners
105055      */
105056     bindStore : function(store, initial){
105057         var me = this;
105058
105059         if (!initial && me.store) {
105060             if(store !== me.store && me.store.autoDestroy){
105061                 me.store.destroyStore();
105062             }else{
105063                 me.store.un('beforeload', me.beforeLoad, me);
105064                 me.store.un('load', me.onLoad, me);
105065                 me.store.un('exception', me.onLoadError, me);
105066             }
105067             if(!store){
105068                 me.store = null;
105069             }
105070         }
105071         if (store) {
105072             store = Ext.data.StoreManager.lookup(store);
105073             store.on({
105074                 scope: me,
105075                 beforeload: me.beforeLoad,
105076                 load: me.onLoad,
105077                 exception: me.onLoadError
105078             });
105079         }
105080         me.store = store;
105081     },
105082
105083     /**
105084      * Unbinds the paging toolbar from the specified {@link Ext.data.Store} **(deprecated)**
105085      * @param {Ext.data.Store} store The data store to unbind
105086      */
105087     unbind : function(store){
105088         this.bindStore(null);
105089     },
105090
105091     /**
105092      * Binds the paging toolbar to the specified {@link Ext.data.Store} **(deprecated)**
105093      * @param {Ext.data.Store} store The data store to bind
105094      */
105095     bind : function(store){
105096         this.bindStore(store);
105097     },
105098
105099     // private
105100     onDestroy : function(){
105101         this.bindStore(null);
105102         this.callParent();
105103     }
105104 });
105105
105106 /**
105107  * An internally used DataView for {@link Ext.form.field.ComboBox ComboBox}.
105108  */
105109 Ext.define('Ext.view.BoundList', {
105110     extend: 'Ext.view.View',
105111     alias: 'widget.boundlist',
105112     alternateClassName: 'Ext.BoundList',
105113     requires: ['Ext.layout.component.BoundList', 'Ext.toolbar.Paging'],
105114
105115     /**
105116      * @cfg {Number} pageSize
105117      * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed at the bottom of the list and store
105118      * queries will execute with page {@link Ext.data.Operation#start start} and
105119      * {@link Ext.data.Operation#limit limit} parameters. Defaults to `0`.
105120      */
105121     pageSize: 0,
105122
105123     /**
105124      * @property {Ext.toolbar.Paging} pagingToolbar
105125      * A reference to the PagingToolbar instance in this view. Only populated if {@link #pageSize} is greater
105126      * than zero and the BoundList has been rendered.
105127      */
105128
105129     // private overrides
105130     autoScroll: true,
105131     baseCls: Ext.baseCSSPrefix + 'boundlist',
105132     itemCls: Ext.baseCSSPrefix + 'boundlist-item',
105133     listItemCls: '',
105134     shadow: false,
105135     trackOver: true,
105136     refreshed: 0,
105137
105138     ariaRole: 'listbox',
105139
105140     componentLayout: 'boundlist',
105141
105142     renderTpl: ['<div id="{id}-listEl" class="list-ct"></div>'],
105143
105144     initComponent: function() {
105145         var me = this,
105146             baseCls = me.baseCls,
105147             itemCls = me.itemCls;
105148             
105149         me.selectedItemCls = baseCls + '-selected';
105150         me.overItemCls = baseCls + '-item-over';
105151         me.itemSelector = "." + itemCls;
105152
105153         if (me.floating) {
105154             me.addCls(baseCls + '-floating');
105155         }
105156
105157         if (!me.tpl) {
105158             // should be setting aria-posinset based on entire set of data
105159             // not filtered set
105160             me.tpl = Ext.create('Ext.XTemplate',
105161                 '<ul><tpl for=".">',
105162                     '<li role="option" class="' + itemCls + '">' + me.getInnerTpl(me.displayField) + '</li>',
105163                 '</tpl></ul>'
105164             );
105165         } else if (Ext.isString(me.tpl)) {
105166             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
105167         }
105168
105169         if (me.pageSize) {
105170             me.pagingToolbar = me.createPagingToolbar();
105171         }
105172
105173         me.callParent();
105174
105175         me.addChildEls('listEl');
105176     },
105177
105178     createPagingToolbar: function() {
105179         return Ext.widget('pagingtoolbar', {
105180             pageSize: this.pageSize,
105181             store: this.store,
105182             border: false
105183         });
105184     },
105185
105186     onRender: function() {
105187         var me = this,
105188             toolbar = me.pagingToolbar;
105189         me.callParent(arguments);
105190         if (toolbar) {
105191             toolbar.render(me.el);
105192         }
105193     },
105194
105195     bindStore : function(store, initial) {
105196         var me = this,
105197             toolbar = me.pagingToolbar;
105198         me.callParent(arguments);
105199         if (toolbar) {
105200             toolbar.bindStore(store, initial);
105201         }
105202     },
105203
105204     getTargetEl: function() {
105205         return this.listEl || this.el;
105206     },
105207
105208     getInnerTpl: function(displayField) {
105209         return '{' + displayField + '}';
105210     },
105211
105212     refresh: function() {
105213         var me = this;
105214         me.callParent();
105215         if (me.isVisible()) {
105216             me.refreshed++;
105217             me.doComponentLayout();
105218             me.refreshed--;
105219         }
105220     },
105221
105222     initAria: function() {
105223         this.callParent();
105224
105225         var selModel = this.getSelectionModel(),
105226             mode     = selModel.getSelectionMode(),
105227             actionEl = this.getActionEl();
105228
105229         // TODO: subscribe to mode changes or allow the selModel to manipulate this attribute.
105230         if (mode !== 'SINGLE') {
105231             actionEl.dom.setAttribute('aria-multiselectable', true);
105232         }
105233     },
105234
105235     onDestroy: function() {
105236         Ext.destroyMembers(this, 'pagingToolbar', 'listEl');
105237         this.callParent();
105238     }
105239 });
105240
105241 /**
105242  * @class Ext.view.BoundListKeyNav
105243  * @extends Ext.util.KeyNav
105244  * A specialized {@link Ext.util.KeyNav} implementation for navigating a {@link Ext.view.BoundList} using
105245  * the keyboard. The up, down, pageup, pagedown, home, and end keys move the active highlight
105246  * through the list. The enter key invokes the selection model's select action using the highlighted item.
105247  */
105248 Ext.define('Ext.view.BoundListKeyNav', {
105249     extend: 'Ext.util.KeyNav',
105250     requires: 'Ext.view.BoundList',
105251
105252     /**
105253      * @cfg {Ext.view.BoundList} boundList (required)
105254      * The {@link Ext.view.BoundList} instance for which key navigation will be managed.
105255      */
105256
105257     constructor: function(el, config) {
105258         var me = this;
105259         me.boundList = config.boundList;
105260         me.callParent([el, Ext.apply({}, config, me.defaultHandlers)]);
105261     },
105262
105263     defaultHandlers: {
105264         up: function() {
105265             var me = this,
105266                 boundList = me.boundList,
105267                 allItems = boundList.all,
105268                 oldItem = boundList.highlightedItem,
105269                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
105270                 newItemIdx = oldItemIdx > 0 ? oldItemIdx - 1 : allItems.getCount() - 1; //wraps around
105271             me.highlightAt(newItemIdx);
105272         },
105273
105274         down: function() {
105275             var me = this,
105276                 boundList = me.boundList,
105277                 allItems = boundList.all,
105278                 oldItem = boundList.highlightedItem,
105279                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
105280                 newItemIdx = oldItemIdx < allItems.getCount() - 1 ? oldItemIdx + 1 : 0; //wraps around
105281             me.highlightAt(newItemIdx);
105282         },
105283
105284         pageup: function() {
105285             //TODO
105286         },
105287
105288         pagedown: function() {
105289             //TODO
105290         },
105291
105292         home: function() {
105293             this.highlightAt(0);
105294         },
105295
105296         end: function() {
105297             var me = this;
105298             me.highlightAt(me.boundList.all.getCount() - 1);
105299         },
105300
105301         enter: function(e) {
105302             this.selectHighlighted(e);
105303         }
105304     },
105305
105306     /**
105307      * Highlights the item at the given index.
105308      * @param {Number} index
105309      */
105310     highlightAt: function(index) {
105311         var boundList = this.boundList,
105312             item = boundList.all.item(index);
105313         if (item) {
105314             item = item.dom;
105315             boundList.highlightItem(item);
105316             boundList.getTargetEl().scrollChildIntoView(item, false);
105317         }
105318     },
105319
105320     /**
105321      * Triggers selection of the currently highlighted item according to the behavior of
105322      * the configured SelectionModel.
105323      */
105324     selectHighlighted: function(e) {
105325         var me = this,
105326             boundList = me.boundList,
105327             highlighted = boundList.highlightedItem,
105328             selModel = boundList.getSelectionModel();
105329         if (highlighted) {
105330             selModel.selectWithEvent(boundList.getRecord(highlighted), e);
105331         }
105332     }
105333
105334 });
105335 /**
105336  * @docauthor Jason Johnston <jason@sencha.com>
105337  *
105338  * A combobox control with support for autocomplete, remote loading, and many other features.
105339  *
105340  * A ComboBox is like a combination of a traditional HTML text `<input>` field and a `<select>`
105341  * field; the user is able to type freely into the field, and/or pick values from a dropdown selection
105342  * list. The user can input any value by default, even if it does not appear in the selection list;
105343  * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`.
105344  *
105345  * The selection list's options are populated from any {@link Ext.data.Store}, including remote
105346  * stores. The data items in the store are mapped to each option's displayed text and backing value via
105347  * the {@link #valueField} and {@link #displayField} configurations, respectively.
105348  *
105349  * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be
105350  * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user.
105351  *
105352  * # Example usage:
105353  *
105354  *     @example
105355  *     // The data store containing the list of states
105356  *     var states = Ext.create('Ext.data.Store', {
105357  *         fields: ['abbr', 'name'],
105358  *         data : [
105359  *             {"abbr":"AL", "name":"Alabama"},
105360  *             {"abbr":"AK", "name":"Alaska"},
105361  *             {"abbr":"AZ", "name":"Arizona"}
105362  *             //...
105363  *         ]
105364  *     });
105365  *
105366  *     // Create the combo box, attached to the states data store
105367  *     Ext.create('Ext.form.ComboBox', {
105368  *         fieldLabel: 'Choose State',
105369  *         store: states,
105370  *         queryMode: 'local',
105371  *         displayField: 'name',
105372  *         valueField: 'abbr',
105373  *         renderTo: Ext.getBody()
105374  *     });
105375  *
105376  * # Events
105377  *
105378  * To do something when something in ComboBox is selected, configure the select event:
105379  *
105380  *     var cb = Ext.create('Ext.form.ComboBox', {
105381  *         // all of your config options
105382  *         listeners:{
105383  *              scope: yourScope,
105384  *              'select': yourFunction
105385  *         }
105386  *     });
105387  *
105388  *     // Alternatively, you can assign events after the object is created:
105389  *     var cb = new Ext.form.field.ComboBox(yourOptions);
105390  *     cb.on('select', yourFunction, yourScope);
105391  *
105392  * # Multiple Selection
105393  *
105394  * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
105395  * {@link #multiSelect} config to `true`.
105396  */
105397 Ext.define('Ext.form.field.ComboBox', {
105398     extend:'Ext.form.field.Picker',
105399     requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager'],
105400     alternateClassName: 'Ext.form.ComboBox',
105401     alias: ['widget.combobox', 'widget.combo'],
105402
105403     /**
105404      * @cfg {String} [triggerCls='x-form-arrow-trigger']
105405      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
105406      * by default and `triggerCls` will be **appended** if specified.
105407      */
105408     triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
105409
105410     /**
105411      * @private
105412      * @cfg {String}
105413      * CSS class used to find the {@link #hiddenDataEl}
105414      */
105415     hiddenDataCls: Ext.baseCSSPrefix + 'hide-display ' + Ext.baseCSSPrefix + 'form-data-hidden',
105416
105417     /**
105418      * @override
105419      */
105420     fieldSubTpl: [
105421         '<div class="{hiddenDataCls}" role="presentation"></div>',
105422         '<input id="{id}" type="{type}" ',
105423             '<tpl if="size">size="{size}" </tpl>',
105424             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
105425             'class="{fieldCls} {typeCls}" autocomplete="off" />',
105426         '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
105427             '{triggerEl}',
105428             '<div class="{clearCls}" role="presentation"></div>',
105429         '</div>',
105430         {
105431             compiled: true,
105432             disableFormats: true
105433         }
105434     ],
105435
105436     getSubTplData: function(){
105437         var me = this;
105438         Ext.applyIf(me.subTplData, {
105439             hiddenDataCls: me.hiddenDataCls
105440         });
105441         return me.callParent(arguments);
105442     },
105443
105444     afterRender: function(){
105445         var me = this;
105446         me.callParent(arguments);
105447         me.setHiddenValue(me.value);
105448     },
105449
105450     /**
105451      * @cfg {Ext.data.Store/Array} store
105452      * The data source to which this combo is bound. Acceptable values for this property are:
105453      *
105454      *   - **any {@link Ext.data.Store Store} subclass**
105455      *   - **an Array** : Arrays will be converted to a {@link Ext.data.Store} internally, automatically generating
105456      *     {@link Ext.data.Field#name field names} to work with all data components.
105457      *
105458      *     - **1-dimensional array** : (e.g., `['Foo','Bar']`)
105459      *
105460      *       A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
105461      *       {@link #valueField} and {@link #displayField})
105462      *
105463      *     - **2-dimensional array** : (e.g., `[['f','Foo'],['b','Bar']]`)
105464      *
105465      *       For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
105466      *       {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
105467      *
105468      * See also {@link #queryMode}.
105469      */
105470
105471     /**
105472      * @cfg {Boolean} multiSelect
105473      * If set to `true`, allows the combo field to hold more than one value at a time, and allows selecting multiple
105474      * items from the dropdown list. The combo's text field will show all selected values separated by the
105475      * {@link #delimiter}.
105476      */
105477     multiSelect: false,
105478
105479     /**
105480      * @cfg {String} delimiter
105481      * The character(s) used to separate the {@link #displayField display values} of multiple selected items when
105482      * `{@link #multiSelect} = true`.
105483      */
105484     delimiter: ', ',
105485
105486     /**
105487      * @cfg {String} displayField
105488      * The underlying {@link Ext.data.Field#name data field name} to bind to this ComboBox.
105489      *
105490      * See also `{@link #valueField}`.
105491      */
105492     displayField: 'text',
105493
105494     /**
105495      * @cfg {String} valueField (required)
105496      * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox (defaults to match
105497      * the value of the {@link #displayField} config).
105498      *
105499      * **Note**: use of a `valueField` requires the user to make a selection in order for a value to be mapped. See also
105500      * `{@link #displayField}`.
105501      */
105502
105503     /**
105504      * @cfg {String} triggerAction
105505      * The action to execute when the trigger is clicked.
105506      *
105507      *   - **`'all'`** :
105508      *
105509      *     {@link #doQuery run the query} specified by the `{@link #allQuery}` config option
105510      *
105511      *   - **`'query'`** :
105512      *
105513      *     {@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.
105514      *
105515      * See also `{@link #queryParam}`.
105516      */
105517     triggerAction: 'all',
105518
105519     /**
105520      * @cfg {String} allQuery
105521      * The text query to send to the server to return all records for the list with no filtering
105522      */
105523     allQuery: '',
105524
105525     /**
105526      * @cfg {String} queryParam
105527      * Name of the parameter used by the Store to pass the typed string when the ComboBox is configured with
105528      * `{@link #queryMode}: 'remote'`. If explicitly set to a falsy value it will not be sent.
105529      */
105530     queryParam: 'query',
105531
105532     /**
105533      * @cfg {String} queryMode
105534      * The mode in which the ComboBox uses the configured Store. Acceptable values are:
105535      *
105536      *   - **`'remote'`** :
105537      *
105538      *     In `queryMode: 'remote'`, the ComboBox loads its Store dynamically based upon user interaction.
105539      *
105540      *     This is typically used for "autocomplete" type inputs, and after the user finishes typing, the Store is {@link
105541      *     Ext.data.Store#load load}ed.
105542      *
105543      *     A parameter containing the typed string is sent in the load request. The default parameter name for the input
105544      *     string is `query`, but this can be configured using the {@link #queryParam} config.
105545      *
105546      *     In `queryMode: 'remote'`, the Store may be configured with `{@link Ext.data.Store#remoteFilter remoteFilter}:
105547      *     true`, and further filters may be _programatically_ added to the Store which are then passed with every load
105548      *     request which allows the server to further refine the returned dataset.
105549      *
105550      *     Typically, in an autocomplete situation, {@link #hideTrigger} is configured `true` because it has no meaning for
105551      *     autocomplete.
105552      *
105553      *   - **`'local'`** :
105554      *
105555      *     ComboBox loads local data
105556      *
105557      *         var combo = new Ext.form.field.ComboBox({
105558      *             renderTo: document.body,
105559      *             queryMode: 'local',
105560      *             store: new Ext.data.ArrayStore({
105561      *                 id: 0,
105562      *                 fields: [
105563      *                     'myId',  // numeric value is the key
105564      *                     'displayText'
105565      *                 ],
105566      *                 data: [[1, 'item1'], [2, 'item2']]  // data is local
105567      *             }),
105568      *             valueField: 'myId',
105569      *             displayField: 'displayText',
105570      *             triggerAction: 'all'
105571      *         });
105572      */
105573     queryMode: 'remote',
105574
105575     queryCaching: true,
105576
105577     /**
105578      * @cfg {Number} pageSize
105579      * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed in the footer of the dropdown list and the
105580      * {@link #doQuery filter queries} will execute with page start and {@link Ext.view.BoundList#pageSize limit}
105581      * parameters. Only applies when `{@link #queryMode} = 'remote'`.
105582      */
105583     pageSize: 0,
105584
105585     /**
105586      * @cfg {Number} queryDelay
105587      * The length of time in milliseconds to delay between the start of typing and sending the query to filter the
105588      * dropdown list (defaults to `500` if `{@link #queryMode} = 'remote'` or `10` if `{@link #queryMode} = 'local'`)
105589      */
105590
105591     /**
105592      * @cfg {Number} minChars
105593      * The minimum number of characters the user must type before autocomplete and {@link #typeAhead} activate (defaults
105594      * to `4` if `{@link #queryMode} = 'remote'` or `0` if `{@link #queryMode} = 'local'`, does not apply if
105595      * `{@link Ext.form.field.Trigger#editable editable} = false`).
105596      */
105597
105598     /**
105599      * @cfg {Boolean} autoSelect
105600      * `true` to automatically highlight the first result gathered by the data store in the dropdown list when it is
105601      * opened. A false value would cause nothing in the list to be highlighted automatically, so
105602      * the user would have to manually highlight an item before pressing the enter or {@link #selectOnTab tab} key to
105603      * select it (unless the value of ({@link #typeAhead}) were true), or use the mouse to select a value.
105604      */
105605     autoSelect: true,
105606
105607     /**
105608      * @cfg {Boolean} typeAhead
105609      * `true` to populate and autoselect the remainder of the text being typed after a configurable delay
105610      * ({@link #typeAheadDelay}) if it matches a known value.
105611      */
105612     typeAhead: false,
105613
105614     /**
105615      * @cfg {Number} typeAheadDelay
105616      * The length of time in milliseconds to wait until the typeahead text is displayed if `{@link #typeAhead} = true`
105617      */
105618     typeAheadDelay: 250,
105619
105620     /**
105621      * @cfg {Boolean} selectOnTab
105622      * Whether the Tab key should select the currently highlighted item.
105623      */
105624     selectOnTab: true,
105625
105626     /**
105627      * @cfg {Boolean} forceSelection
105628      * `true` to restrict the selected value to one of the values in the list, `false` to allow the user to set
105629      * arbitrary text into the field.
105630      */
105631     forceSelection: false,
105632
105633     /**
105634      * @cfg {String} valueNotFoundText
105635      * When using a name/value combo, if the value passed to setValue is not found in the store, valueNotFoundText will
105636      * be displayed as the field text if defined. If this default text is used, it means there
105637      * is no value set and no validation will occur on this field.
105638      */
105639
105640     /**
105641      * @property {String} lastQuery
105642      * The value of the match string used to filter the store. Delete this property to force a requery. Example use:
105643      *
105644      *     var combo = new Ext.form.field.ComboBox({
105645      *         ...
105646      *         queryMode: 'remote',
105647      *         listeners: {
105648      *             // delete the previous query in the beforequery event or set
105649      *             // combo.lastQuery = null (this will reload the store the next time it expands)
105650      *             beforequery: function(qe){
105651      *                 delete qe.combo.lastQuery;
105652      *             }
105653      *         }
105654      *     });
105655      *
105656      * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used configure the
105657      * combo with `lastQuery=''`. Example use:
105658      *
105659      *     var combo = new Ext.form.field.ComboBox({
105660      *         ...
105661      *         queryMode: 'local',
105662      *         triggerAction: 'all',
105663      *         lastQuery: ''
105664      *     });
105665      */
105666
105667     /**
105668      * @cfg {Object} defaultListConfig
105669      * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.
105670      */
105671     defaultListConfig: {
105672         emptyText: '',
105673         loadingText: 'Loading...',
105674         loadingHeight: 70,
105675         minWidth: 70,
105676         maxHeight: 300,
105677         shadow: 'sides'
105678     },
105679
105680     /**
105681      * @cfg {String/HTMLElement/Ext.Element} transform
105682      * The id, DOM node or {@link Ext.Element} of an existing HTML `<select>` element to convert into a ComboBox. The
105683      * target select's options will be used to build the options in the ComboBox dropdown; a configured {@link #store}
105684      * will take precedence over this.
105685      */
105686
105687     /**
105688      * @cfg {Object} listConfig
105689      * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor.
105690      * Any configuration that is valid for BoundList can be included. Some of the more useful ones are:
105691      *
105692      *   - {@link Ext.view.BoundList#cls} - defaults to empty
105693      *   - {@link Ext.view.BoundList#emptyText} - defaults to empty string
105694      *   - {@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList
105695      *   - {@link Ext.view.BoundList#loadingText} - defaults to `'Loading...'`
105696      *   - {@link Ext.view.BoundList#minWidth} - defaults to `70`
105697      *   - {@link Ext.view.BoundList#maxWidth} - defaults to `undefined`
105698      *   - {@link Ext.view.BoundList#maxHeight} - defaults to `300`
105699      *   - {@link Ext.view.BoundList#resizable} - defaults to `false`
105700      *   - {@link Ext.view.BoundList#shadow} - defaults to `'sides'`
105701      *   - {@link Ext.view.BoundList#width} - defaults to `undefined` (automatically set to the width of the ComboBox
105702      *     field if {@link #matchFieldWidth} is true)
105703      */
105704
105705     //private
105706     ignoreSelection: 0,
105707
105708     initComponent: function() {
105709         var me = this,
105710             isDefined = Ext.isDefined,
105711             store = me.store,
105712             transform = me.transform,
105713             transformSelect, isLocalMode;
105714
105715         Ext.applyIf(me.renderSelectors, {
105716             hiddenDataEl: '.' + me.hiddenDataCls.split(' ').join('.')
105717         });
105718         
105719         if (me.typeAhead && me.multiSelect) {
105720             Ext.Error.raise('typeAhead and multiSelect are mutually exclusive options -- please remove one of them.');
105721         }
105722         if (me.typeAhead && !me.editable) {
105723             Ext.Error.raise('If typeAhead is enabled the combo must be editable: true -- please change one of those settings.');
105724         }
105725         if (me.selectOnFocus && !me.editable) {
105726             Ext.Error.raise('If selectOnFocus is enabled the combo must be editable: true -- please change one of those settings.');
105727         }
105728
105729         this.addEvents(
105730             /**
105731              * @event beforequery
105732              * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's cancel
105733              * property to true.
105734              *
105735              * @param {Object} queryEvent An object that has these properties:
105736              *
105737              *   - `combo` : Ext.form.field.ComboBox
105738              *
105739              *     This combo box
105740              *
105741              *   - `query` : String
105742              *
105743              *     The query string
105744              *
105745              *   - `forceAll` : Boolean
105746              *
105747              *     True to force "all" query
105748              *
105749              *   - `cancel` : Boolean
105750              *
105751              *     Set to true to cancel the query
105752              */
105753             'beforequery',
105754
105755             /**
105756              * @event select
105757              * Fires when at least one list item is selected.
105758              * @param {Ext.form.field.ComboBox} combo This combo box
105759              * @param {Array} records The selected records
105760              */
105761             'select',
105762
105763             /**
105764              * @event beforeselect
105765              * Fires before the selected item is added to the collection
105766              * @param {Ext.form.field.ComboBox} combo This combo box
105767              * @param {Ext.data.Record} record The selected record
105768              * @param {Number} index The index of the selected record
105769              */
105770             'beforeselect',
105771
105772             /**
105773              * @event beforedeselect
105774              * Fires before the deselected item is removed from the collection
105775              * @param {Ext.form.field.ComboBox} combo This combo box
105776              * @param {Ext.data.Record} record The deselected record
105777              * @param {Number} index The index of the deselected record
105778              */
105779             'beforedeselect'
105780         );
105781
105782         // Build store from 'transform' HTML select element's options
105783         if (transform) {
105784             transformSelect = Ext.getDom(transform);
105785             if (transformSelect) {
105786                 store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option) {
105787                     return [option.value, option.text];
105788                 });
105789                 if (!me.name) {
105790                     me.name = transformSelect.name;
105791                 }
105792                 if (!('value' in me)) {
105793                     me.value = transformSelect.value;
105794                 }
105795             }
105796         }
105797
105798         me.bindStore(store || 'ext-empty-store', true);
105799         store = me.store;
105800         if (store.autoCreated) {
105801             me.queryMode = 'local';
105802             me.valueField = me.displayField = 'field1';
105803             if (!store.expanded) {
105804                 me.displayField = 'field2';
105805             }
105806         }
105807
105808
105809         if (!isDefined(me.valueField)) {
105810             me.valueField = me.displayField;
105811         }
105812
105813         isLocalMode = me.queryMode === 'local';
105814         if (!isDefined(me.queryDelay)) {
105815             me.queryDelay = isLocalMode ? 10 : 500;
105816         }
105817         if (!isDefined(me.minChars)) {
105818             me.minChars = isLocalMode ? 0 : 4;
105819         }
105820
105821         if (!me.displayTpl) {
105822             me.displayTpl = Ext.create('Ext.XTemplate',
105823                 '<tpl for=".">' +
105824                     '{[typeof values === "string" ? values : values["' + me.displayField + '"]]}' +
105825                     '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
105826                 '</tpl>'
105827             );
105828         } else if (Ext.isString(me.displayTpl)) {
105829             me.displayTpl = Ext.create('Ext.XTemplate', me.displayTpl);
105830         }
105831
105832         me.callParent();
105833
105834         me.doQueryTask = Ext.create('Ext.util.DelayedTask', me.doRawQuery, me);
105835
105836         // store has already been loaded, setValue
105837         if (me.store.getCount() > 0) {
105838             me.setValue(me.value);
105839         }
105840
105841         // render in place of 'transform' select
105842         if (transformSelect) {
105843             me.render(transformSelect.parentNode, transformSelect);
105844             Ext.removeNode(transformSelect);
105845             delete me.renderTo;
105846         }
105847     },
105848
105849     /**
105850      * Returns the store associated with this ComboBox.
105851      * @return {Ext.data.Store} The store
105852      */
105853     getStore : function(){
105854         return this.store;
105855     },
105856
105857     beforeBlur: function() {
105858         this.doQueryTask.cancel();
105859         this.assertValue();
105860     },
105861
105862     // private
105863     assertValue: function() {
105864         var me = this,
105865             value = me.getRawValue(),
105866             rec;
105867
105868         if (me.forceSelection) {
105869             if (me.multiSelect) {
105870                 // For multiselect, check that the current displayed value matches the current
105871                 // selection, if it does not then revert to the most recent selection.
105872                 if (value !== me.getDisplayValue()) {
105873                     me.setValue(me.lastSelection);
105874                 }
105875             } else {
105876                 // For single-select, match the displayed value to a record and select it,
105877                 // if it does not match a record then revert to the most recent selection.
105878                 rec = me.findRecordByDisplay(value);
105879                 if (rec) {
105880                     me.select(rec);
105881                 } else {
105882                     me.setValue(me.lastSelection);
105883                 }
105884             }
105885         }
105886         me.collapse();
105887     },
105888
105889     onTypeAhead: function() {
105890         var me = this,
105891             displayField = me.displayField,
105892             record = me.store.findRecord(displayField, me.getRawValue()),
105893             boundList = me.getPicker(),
105894             newValue, len, selStart;
105895
105896         if (record) {
105897             newValue = record.get(displayField);
105898             len = newValue.length;
105899             selStart = me.getRawValue().length;
105900
105901             boundList.highlightItem(boundList.getNode(record));
105902
105903             if (selStart !== 0 && selStart !== len) {
105904                 me.setRawValue(newValue);
105905                 me.selectText(selStart, newValue.length);
105906             }
105907         }
105908     },
105909
105910     // invoked when a different store is bound to this combo
105911     // than the original
105912     resetToDefault: function() {
105913
105914     },
105915
105916     bindStore: function(store, initial) {
105917         var me = this,
105918             oldStore = me.store;
105919
105920         // this code directly accesses this.picker, bc invoking getPicker
105921         // would create it when we may be preping to destroy it
105922         if (oldStore && !initial) {
105923             if (oldStore !== store && oldStore.autoDestroy) {
105924                 oldStore.destroyStore();
105925             } else {
105926                 oldStore.un({
105927                     scope: me,
105928                     load: me.onLoad,
105929                     exception: me.collapse
105930                 });
105931             }
105932             if (!store) {
105933                 me.store = null;
105934                 if (me.picker) {
105935                     me.picker.bindStore(null);
105936                 }
105937             }
105938         }
105939         if (store) {
105940             if (!initial) {
105941                 me.resetToDefault();
105942             }
105943
105944             me.store = Ext.data.StoreManager.lookup(store);
105945             me.store.on({
105946                 scope: me,
105947                 load: me.onLoad,
105948                 exception: me.collapse
105949             });
105950
105951             if (me.picker) {
105952                 me.picker.bindStore(store);
105953             }
105954         }
105955     },
105956
105957     onLoad: function() {
105958         var me = this,
105959             value = me.value;
105960
105961         // If performing a remote query upon the raw value...
105962         if (me.rawQuery) {
105963             me.rawQuery = false;
105964             me.syncSelection();
105965             if (me.picker && !me.picker.getSelectionModel().hasSelection()) {
105966                 me.doAutoSelect();
105967             }
105968         }
105969         // If store initial load or triggerAction: 'all' trigger click.
105970         else {
105971             // Set the value on load
105972             if (me.value) {
105973                 me.setValue(me.value);
105974             } else {
105975                 // There's no value.
105976                 // Highlight the first item in the list if autoSelect: true
105977                 if (me.store.getCount()) {
105978                     me.doAutoSelect();
105979                 } else {
105980                     me.setValue('');
105981                 }
105982             }
105983         }
105984     },
105985
105986     /**
105987      * @private
105988      * Execute the query with the raw contents within the textfield.
105989      */
105990     doRawQuery: function() {
105991         this.doQuery(this.getRawValue(), false, true);
105992     },
105993
105994     /**
105995      * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the query
105996      * allowing the query action to be canceled if needed.
105997      *
105998      * @param {String} queryString The SQL query to execute
105999      * @param {Boolean} [forceAll=false] `true` to force the query to execute even if there are currently fewer characters in
106000      * the field than the minimum specified by the `{@link #minChars}` config option. It also clears any filter
106001      * previously saved in the current store.
106002      * @param {Boolean} [rawQuery=false] Pass as true if the raw typed value is being used as the query string. This causes the
106003      * resulting store load to leave the raw value undisturbed.
106004      * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery}
106005      * handler.
106006      */
106007     doQuery: function(queryString, forceAll, rawQuery) {
106008         queryString = queryString || '';
106009
106010         // store in object and pass by reference in 'beforequery'
106011         // so that client code can modify values.
106012         var me = this,
106013             qe = {
106014                 query: queryString,
106015                 forceAll: forceAll,
106016                 combo: me,
106017                 cancel: false
106018             },
106019             store = me.store,
106020             isLocalMode = me.queryMode === 'local';
106021
106022         if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
106023             return false;
106024         }
106025
106026         // get back out possibly modified values
106027         queryString = qe.query;
106028         forceAll = qe.forceAll;
106029
106030         // query permitted to run
106031         if (forceAll || (queryString.length >= me.minChars)) {
106032             // expand before starting query so LoadMask can position itself correctly
106033             me.expand();
106034
106035             // make sure they aren't querying the same thing
106036             if (!me.queryCaching || me.lastQuery !== queryString) {
106037                 me.lastQuery = queryString;
106038
106039                 if (isLocalMode) {
106040                     // forceAll means no filtering - show whole dataset.
106041                     if (forceAll) {
106042                         store.clearFilter();
106043                     } else {
106044                         // Clear filter, but supress event so that the BoundList is not immediately updated.
106045                         store.clearFilter(true);
106046                         store.filter(me.displayField, queryString);
106047                     }
106048                 } else {
106049                     // Set flag for onLoad handling to know how the Store was loaded
106050                     me.rawQuery = rawQuery;
106051
106052                     // In queryMode: 'remote', we assume Store filters are added by the developer as remote filters,
106053                     // and these are automatically passed as params with every load call, so we do *not* call clearFilter.
106054                     if (me.pageSize) {
106055                         // if we're paging, we've changed the query so start at page 1.
106056                         me.loadPage(1);
106057                     } else {
106058                         store.load({
106059                             params: me.getParams(queryString)
106060                         });
106061                     }
106062                 }
106063             }
106064
106065             // Clear current selection if it does not match the current value in the field
106066             if (me.getRawValue() !== me.getDisplayValue()) {
106067                 me.ignoreSelection++;
106068                 me.picker.getSelectionModel().deselectAll();
106069                 me.ignoreSelection--;
106070             }
106071
106072             if (isLocalMode) {
106073                 me.doAutoSelect();
106074             }
106075             if (me.typeAhead) {
106076                 me.doTypeAhead();
106077             }
106078         }
106079         return true;
106080     },
106081
106082     loadPage: function(pageNum){
106083         this.store.loadPage(pageNum, {
106084             params: this.getParams(this.lastQuery)
106085         });
106086     },
106087
106088     onPageChange: function(toolbar, newPage){
106089         /*
106090          * Return false here so we can call load ourselves and inject the query param.
106091          * We don't want to do this for every store load since the developer may load
106092          * the store through some other means so we won't add the query param.
106093          */
106094         this.loadPage(newPage);
106095         return false;
106096     },
106097
106098     // private
106099     getParams: function(queryString) {
106100         var params = {},
106101             param = this.queryParam;
106102
106103         if (param) {
106104             params[param] = queryString;
106105         }
106106         return params;
106107     },
106108
106109     /**
106110      * @private
106111      * If the autoSelect config is true, and the picker is open, highlights the first item.
106112      */
106113     doAutoSelect: function() {
106114         var me = this,
106115             picker = me.picker,
106116             lastSelected, itemNode;
106117         if (picker && me.autoSelect && me.store.getCount() > 0) {
106118             // Highlight the last selected item and scroll it into view
106119             lastSelected = picker.getSelectionModel().lastSelected;
106120             itemNode = picker.getNode(lastSelected || 0);
106121             if (itemNode) {
106122                 picker.highlightItem(itemNode);
106123                 picker.listEl.scrollChildIntoView(itemNode, false);
106124             }
106125         }
106126     },
106127
106128     doTypeAhead: function() {
106129         if (!this.typeAheadTask) {
106130             this.typeAheadTask = Ext.create('Ext.util.DelayedTask', this.onTypeAhead, this);
106131         }
106132         if (this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE) {
106133             this.typeAheadTask.delay(this.typeAheadDelay);
106134         }
106135     },
106136
106137     onTriggerClick: function() {
106138         var me = this;
106139         if (!me.readOnly && !me.disabled) {
106140             if (me.isExpanded) {
106141                 me.collapse();
106142             } else {
106143                 me.onFocus({});
106144                 if (me.triggerAction === 'all') {
106145                     me.doQuery(me.allQuery, true);
106146                 } else {
106147                     me.doQuery(me.getRawValue(), false, true);
106148                 }
106149             }
106150             me.inputEl.focus();
106151         }
106152     },
106153
106154
106155     // store the last key and doQuery if relevant
106156     onKeyUp: function(e, t) {
106157         var me = this,
106158             key = e.getKey();
106159
106160         if (!me.readOnly && !me.disabled && me.editable) {
106161             me.lastKey = key;
106162             // we put this in a task so that we can cancel it if a user is
106163             // in and out before the queryDelay elapses
106164
106165             // perform query w/ any normal key or backspace or delete
106166             if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
106167                 me.doQueryTask.delay(me.queryDelay);
106168             }
106169         }
106170
106171         if (me.enableKeyEvents) {
106172             me.callParent(arguments);
106173         }
106174     },
106175
106176     initEvents: function() {
106177         var me = this;
106178         me.callParent();
106179
106180         /*
106181          * Setup keyboard handling. If enableKeyEvents is true, we already have
106182          * a listener on the inputEl for keyup, so don't create a second.
106183          */
106184         if (!me.enableKeyEvents) {
106185             me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
106186         }
106187     },
106188     
106189     onDestroy: function(){
106190         this.bindStore(null);
106191         this.callParent();    
106192     },
106193
106194     createPicker: function() {
106195         var me = this,
106196             picker,
106197             menuCls = Ext.baseCSSPrefix + 'menu',
106198             opts = Ext.apply({
106199                 pickerField: me,
106200                 selModel: {
106201                     mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
106202                 },
106203                 floating: true,
106204                 hidden: true,
106205                 ownerCt: me.ownerCt,
106206                 cls: me.el.up('.' + menuCls) ? menuCls : '',
106207                 store: me.store,
106208                 displayField: me.displayField,
106209                 focusOnToFront: false,
106210                 pageSize: me.pageSize,
106211                 tpl: me.tpl
106212             }, me.listConfig, me.defaultListConfig);
106213
106214         picker = me.picker = Ext.create('Ext.view.BoundList', opts);
106215         if (me.pageSize) {
106216             picker.pagingToolbar.on('beforechange', me.onPageChange, me);
106217         }
106218
106219         me.mon(picker, {
106220             itemclick: me.onItemClick,
106221             refresh: me.onListRefresh,
106222             scope: me
106223         });
106224
106225         me.mon(picker.getSelectionModel(), {
106226             'beforeselect': me.onBeforeSelect,
106227             'beforedeselect': me.onBeforeDeselect,
106228             'selectionchange': me.onListSelectionChange,
106229             scope: me
106230         });
106231
106232         return picker;
106233     },
106234
106235     alignPicker: function(){
106236         var me = this,
106237             picker = me.picker,
106238             heightAbove = me.getPosition()[1] - Ext.getBody().getScroll().top,
106239             heightBelow = Ext.Element.getViewHeight() - heightAbove - me.getHeight(),
106240             space = Math.max(heightAbove, heightBelow);
106241
106242         me.callParent();
106243         if (picker.getHeight() > space) {
106244             picker.setHeight(space - 5); // have some leeway so we aren't flush against
106245             me.doAlign();
106246         }
106247     },
106248
106249     onListRefresh: function() {
106250         this.alignPicker();
106251         this.syncSelection();
106252     },
106253
106254     onItemClick: function(picker, record){
106255         /*
106256          * If we're doing single selection, the selection change events won't fire when
106257          * clicking on the selected element. Detect it here.
106258          */
106259         var me = this,
106260             lastSelection = me.lastSelection,
106261             valueField = me.valueField,
106262             selected;
106263
106264         if (!me.multiSelect && lastSelection) {
106265             selected = lastSelection[0];
106266             if (selected && (record.get(valueField) === selected.get(valueField))) {
106267                 // Make sure we also update the display value if it's only partial
106268                 me.displayTplData = [record.data];
106269                 me.setRawValue(me.getDisplayValue());
106270                 me.collapse();
106271             }
106272         }
106273     },
106274
106275     onBeforeSelect: function(list, record) {
106276         return this.fireEvent('beforeselect', this, record, record.index);
106277     },
106278
106279     onBeforeDeselect: function(list, record) {
106280         return this.fireEvent('beforedeselect', this, record, record.index);
106281     },
106282
106283     onListSelectionChange: function(list, selectedRecords) {
106284         var me = this,
106285             isMulti = me.multiSelect,
106286             hasRecords = selectedRecords.length > 0;
106287         // Only react to selection if it is not called from setValue, and if our list is
106288         // expanded (ignores changes to the selection model triggered elsewhere)
106289         if (!me.ignoreSelection && me.isExpanded) {
106290             if (!isMulti) {
106291                 Ext.defer(me.collapse, 1, me);
106292             }
106293             /*
106294              * Only set the value here if we're in multi selection mode or we have
106295              * a selection. Otherwise setValue will be called with an empty value
106296              * which will cause the change event to fire twice.
106297              */
106298             if (isMulti || hasRecords) {
106299                 me.setValue(selectedRecords, false);
106300             }
106301             if (hasRecords) {
106302                 me.fireEvent('select', me, selectedRecords);
106303             }
106304             me.inputEl.focus();
106305         }
106306     },
106307
106308     /**
106309      * @private
106310      * Enables the key nav for the BoundList when it is expanded.
106311      */
106312     onExpand: function() {
106313         var me = this,
106314             keyNav = me.listKeyNav,
106315             selectOnTab = me.selectOnTab,
106316             picker = me.getPicker();
106317
106318         // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
106319         if (keyNav) {
106320             keyNav.enable();
106321         } else {
106322             keyNav = me.listKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
106323                 boundList: picker,
106324                 forceKeyDown: true,
106325                 tab: function(e) {
106326                     if (selectOnTab) {
106327                         this.selectHighlighted(e);
106328                         me.triggerBlur();
106329                     }
106330                     // Tab key event is allowed to propagate to field
106331                     return true;
106332                 }
106333             });
106334         }
106335
106336         // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
106337         if (selectOnTab) {
106338             me.ignoreMonitorTab = true;
106339         }
106340
106341         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
106342         me.inputEl.focus();
106343     },
106344
106345     /**
106346      * @private
106347      * Disables the key nav for the BoundList when it is collapsed.
106348      */
106349     onCollapse: function() {
106350         var me = this,
106351             keyNav = me.listKeyNav;
106352         if (keyNav) {
106353             keyNav.disable();
106354             me.ignoreMonitorTab = false;
106355         }
106356     },
106357
106358     /**
106359      * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
106360      * @param {Object} r
106361      */
106362     select: function(r) {
106363         this.setValue(r, true);
106364     },
106365
106366     /**
106367      * Finds the record by searching for a specific field/value combination.
106368      * @param {String} field The name of the field to test.
106369      * @param {Object} value The value to match the field against.
106370      * @return {Ext.data.Model} The matched record or false.
106371      */
106372     findRecord: function(field, value) {
106373         var ds = this.store,
106374             idx = ds.findExact(field, value);
106375         return idx !== -1 ? ds.getAt(idx) : false;
106376     },
106377
106378     /**
106379      * Finds the record by searching values in the {@link #valueField}.
106380      * @param {Object} value The value to match the field against.
106381      * @return {Ext.data.Model} The matched record or false.
106382      */
106383     findRecordByValue: function(value) {
106384         return this.findRecord(this.valueField, value);
106385     },
106386
106387     /**
106388      * Finds the record by searching values in the {@link #displayField}.
106389      * @param {Object} value The value to match the field against.
106390      * @return {Ext.data.Model} The matched record or false.
106391      */
106392     findRecordByDisplay: function(value) {
106393         return this.findRecord(this.displayField, value);
106394     },
106395
106396     /**
106397      * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that
106398      * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the
106399      * field. If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be
106400      * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.
106401      * @param {String/String[]} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
106402      * or an Array of Strings or Models.
106403      * @return {Ext.form.field.Field} this
106404      */
106405     setValue: function(value, doSelect) {
106406         var me = this,
106407             valueNotFoundText = me.valueNotFoundText,
106408             inputEl = me.inputEl,
106409             i, len, record,
106410             models = [],
106411             displayTplData = [],
106412             processedValue = [];
106413
106414         if (me.store.loading) {
106415             // Called while the Store is loading. Ensure it is processed by the onLoad method.
106416             me.value = value;
106417             me.setHiddenValue(me.value);
106418             return me;
106419         }
106420
106421         // This method processes multi-values, so ensure value is an array.
106422         value = Ext.Array.from(value);
106423
106424         // Loop through values
106425         for (i = 0, len = value.length; i < len; i++) {
106426             record = value[i];
106427             if (!record || !record.isModel) {
106428                 record = me.findRecordByValue(record);
106429             }
106430             // record found, select it.
106431             if (record) {
106432                 models.push(record);
106433                 displayTplData.push(record.data);
106434                 processedValue.push(record.get(me.valueField));
106435             }
106436             // record was not found, this could happen because
106437             // store is not loaded or they set a value not in the store
106438             else {
106439                 // If we are allowing insertion of values not represented in the Store, then set the value, and the display value
106440                 if (!me.forceSelection) {
106441                     displayTplData.push(value[i]);
106442                     processedValue.push(value[i]);
106443                 }
106444                 // Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
106445                 else if (Ext.isDefined(valueNotFoundText)) {
106446                     displayTplData.push(valueNotFoundText);
106447                 }
106448             }
106449         }
106450
106451         // Set the value of this field. If we are multiselecting, then that is an array.
106452         me.setHiddenValue(processedValue);
106453         me.value = me.multiSelect ? processedValue : processedValue[0];
106454         if (!Ext.isDefined(me.value)) {
106455             me.value = null;
106456         }
106457         me.displayTplData = displayTplData; //store for getDisplayValue method
106458         me.lastSelection = me.valueModels = models;
106459
106460         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
106461             inputEl.removeCls(me.emptyCls);
106462         }
106463
106464         // Calculate raw value from the collection of Model data
106465         me.setRawValue(me.getDisplayValue());
106466         me.checkChange();
106467
106468         if (doSelect !== false) {
106469             me.syncSelection();
106470         }
106471         me.applyEmptyText();
106472
106473         return me;
106474     },
106475
106476     /**
106477      * @private
106478      * Set the value of {@link #hiddenDataEl}
106479      * Dynamically adds and removes input[type=hidden] elements
106480      */
106481     setHiddenValue: function(values){
106482         var me = this, i;
106483         if (!me.hiddenDataEl) {
106484             return;
106485         }
106486         values = Ext.Array.from(values);
106487         var dom = me.hiddenDataEl.dom,
106488             childNodes = dom.childNodes,
106489             input = childNodes[0],
106490             valueCount = values.length,
106491             childrenCount = childNodes.length;
106492         
106493         if (!input && valueCount > 0) {
106494             me.hiddenDataEl.update(Ext.DomHelper.markup({tag:'input', type:'hidden', name:me.name}));
106495             childrenCount = 1;
106496             input = dom.firstChild;
106497         }
106498         while (childrenCount > valueCount) {
106499             dom.removeChild(childNodes[0]);
106500             -- childrenCount;
106501         }
106502         while (childrenCount < valueCount) {
106503             dom.appendChild(input.cloneNode(true));
106504             ++ childrenCount;
106505         }
106506         for (i = 0; i < valueCount; i++) {
106507             childNodes[i].value = values[i];
106508         }
106509     },
106510
106511     /**
106512      * @private Generates the string value to be displayed in the text field for the currently stored value
106513      */
106514     getDisplayValue: function() {
106515         return this.displayTpl.apply(this.displayTplData);
106516     },
106517
106518     getValue: function() {
106519         // If the user has not changed the raw field value since a value was selected from the list,
106520         // then return the structured value from the selection. If the raw field value is different
106521         // than what would be displayed due to selection, return that raw value.
106522         var me = this,
106523             picker = me.picker,
106524             rawValue = me.getRawValue(), //current value of text field
106525             value = me.value; //stored value from last selection or setValue() call
106526
106527         if (me.getDisplayValue() !== rawValue) {
106528             value = rawValue;
106529             me.value = me.displayTplData = me.valueModels = null;
106530             if (picker) {
106531                 me.ignoreSelection++;
106532                 picker.getSelectionModel().deselectAll();
106533                 me.ignoreSelection--;
106534             }
106535         }
106536
106537         return value;
106538     },
106539
106540     getSubmitValue: function() {
106541         return this.getValue();
106542     },
106543
106544     isEqual: function(v1, v2) {
106545         var fromArray = Ext.Array.from,
106546             i, len;
106547
106548         v1 = fromArray(v1);
106549         v2 = fromArray(v2);
106550         len = v1.length;
106551
106552         if (len !== v2.length) {
106553             return false;
106554         }
106555
106556         for(i = 0; i < len; i++) {
106557             if (v2[i] !== v1[i]) {
106558                 return false;
106559             }
106560         }
106561
106562         return true;
106563     },
106564
106565     /**
106566      * Clears any value currently set in the ComboBox.
106567      */
106568     clearValue: function() {
106569         this.setValue([]);
106570     },
106571
106572     /**
106573      * @private Synchronizes the selection in the picker to match the current value of the combobox.
106574      */
106575     syncSelection: function() {
106576         var me = this,
106577             ExtArray = Ext.Array,
106578             picker = me.picker,
106579             selection, selModel;
106580         if (picker) {
106581             // From the value, find the Models that are in the store's current data
106582             selection = [];
106583             ExtArray.forEach(me.valueModels || [], function(value) {
106584                 if (value && value.isModel && me.store.indexOf(value) >= 0) {
106585                     selection.push(value);
106586                 }
106587             });
106588
106589             // Update the selection to match
106590             me.ignoreSelection++;
106591             selModel = picker.getSelectionModel();
106592             selModel.deselectAll();
106593             if (selection.length) {
106594                 selModel.select(selection);
106595             }
106596             me.ignoreSelection--;
106597         }
106598     }
106599 });
106600
106601 /**
106602  * A month picker component. This class is used by the {@link Ext.picker.Date Date picker} class
106603  * to allow browsing and selection of year/months combinations.
106604  */
106605 Ext.define('Ext.picker.Month', {
106606     extend: 'Ext.Component',
106607     requires: ['Ext.XTemplate', 'Ext.util.ClickRepeater', 'Ext.Date', 'Ext.button.Button'],
106608     alias: 'widget.monthpicker',
106609     alternateClassName: 'Ext.MonthPicker',
106610
106611     renderTpl: [
106612         '<div id="{id}-bodyEl" class="{baseCls}-body">',
106613           '<div class="{baseCls}-months">',
106614               '<tpl for="months">',
106615                   '<div class="{parent.baseCls}-item {parent.baseCls}-month"><a href="#" hidefocus="on">{.}</a></div>',
106616               '</tpl>',
106617           '</div>',
106618           '<div class="{baseCls}-years">',
106619               '<div class="{baseCls}-yearnav">',
106620                   '<button id="{id}-prevEl" class="{baseCls}-yearnav-prev"></button>',
106621                   '<button id="{id}-nextEl" class="{baseCls}-yearnav-next"></button>',
106622               '</div>',
106623               '<tpl for="years">',
106624                   '<div class="{parent.baseCls}-item {parent.baseCls}-year"><a href="#" hidefocus="on">{.}</a></div>',
106625               '</tpl>',
106626           '</div>',
106627           '<div class="' + Ext.baseCSSPrefix + 'clear"></div>',
106628         '</div>',
106629         '<tpl if="showButtons">',
106630           '<div id="{id}-buttonsEl" class="{baseCls}-buttons"></div>',
106631         '</tpl>'
106632     ],
106633
106634     /**
106635      * @cfg {String} okText The text to display on the ok button.
106636      */
106637     okText: 'OK',
106638
106639     /**
106640      * @cfg {String} cancelText The text to display on the cancel button.
106641      */
106642     cancelText: 'Cancel',
106643
106644     /**
106645      * @cfg {String} baseCls The base CSS class to apply to the picker element. Defaults to <tt>'x-monthpicker'</tt>
106646      */
106647     baseCls: Ext.baseCSSPrefix + 'monthpicker',
106648
106649     /**
106650      * @cfg {Boolean} showButtons True to show ok and cancel buttons below the picker.
106651      */
106652     showButtons: true,
106653
106654     /**
106655      * @cfg {String} selectedCls The class to be added to selected items in the picker. Defaults to
106656      * <tt>'x-monthpicker-selected'</tt>
106657      */
106658
106659     /**
106660      * @cfg {Date/Number[]} value The default value to set. See {@link #setValue}
106661      */
106662     width: 178,
106663
106664     // used when attached to date picker which isnt showing buttons
106665     smallCls: Ext.baseCSSPrefix + 'monthpicker-small',
106666
106667     // private
106668     totalYears: 10,
106669     yearOffset: 5, // 10 years in total, 2 per row
106670     monthOffset: 6, // 12 months, 2 per row
106671
106672     // private, inherit docs
106673     initComponent: function(){
106674         var me = this;
106675
106676         me.selectedCls = me.baseCls + '-selected';
106677         me.addEvents(
106678             /**
106679              * @event cancelclick
106680              * Fires when the cancel button is pressed.
106681              * @param {Ext.picker.Month} this
106682              */
106683             'cancelclick',
106684
106685             /**
106686              * @event monthclick
106687              * Fires when a month is clicked.
106688              * @param {Ext.picker.Month} this
106689              * @param {Array} value The current value
106690              */
106691             'monthclick',
106692
106693             /**
106694              * @event monthdblclick
106695              * Fires when a month is clicked.
106696              * @param {Ext.picker.Month} this
106697              * @param {Array} value The current value
106698              */
106699             'monthdblclick',
106700
106701             /**
106702              * @event okclick
106703              * Fires when the ok button is pressed.
106704              * @param {Ext.picker.Month} this
106705              * @param {Array} value The current value
106706              */
106707             'okclick',
106708
106709             /**
106710              * @event select
106711              * Fires when a month/year is selected.
106712              * @param {Ext.picker.Month} this
106713              * @param {Array} value The current value
106714              */
106715             'select',
106716
106717             /**
106718              * @event yearclick
106719              * Fires when a year is clicked.
106720              * @param {Ext.picker.Month} this
106721              * @param {Array} value The current value
106722              */
106723             'yearclick',
106724
106725             /**
106726              * @event yeardblclick
106727              * Fires when a year is clicked.
106728              * @param {Ext.picker.Month} this
106729              * @param {Array} value The current value
106730              */
106731             'yeardblclick'
106732         );
106733         if (me.small) {
106734             me.addCls(me.smallCls);
106735         }
106736         me.setValue(me.value);
106737         me.activeYear = me.getYear(new Date().getFullYear() - 4, -4);
106738         this.callParent();
106739     },
106740
106741     // private, inherit docs
106742     onRender: function(ct, position){
106743         var me = this,
106744             i = 0,
106745             months = [],
106746             shortName = Ext.Date.getShortMonthName,
106747             monthLen = me.monthOffset;
106748
106749         for (; i < monthLen; ++i) {
106750             months.push(shortName(i), shortName(i + monthLen));
106751         }
106752
106753         Ext.apply(me.renderData, {
106754             months: months,
106755             years: me.getYears(),
106756             showButtons: me.showButtons
106757         });
106758
106759         me.addChildEls('bodyEl', 'prevEl', 'nextEl', 'buttonsEl');
106760
106761         me.callParent(arguments);
106762     },
106763
106764     // private, inherit docs
106765     afterRender: function(){
106766         var me = this,
106767             body = me.bodyEl,
106768             buttonsEl = me.buttonsEl;
106769
106770         me.callParent();
106771
106772         me.mon(body, 'click', me.onBodyClick, me);
106773         me.mon(body, 'dblclick', me.onBodyClick, me);
106774
106775         // keep a reference to the year/month elements since we'll be re-using them
106776         me.years = body.select('.' + me.baseCls + '-year a');
106777         me.months = body.select('.' + me.baseCls + '-month a');
106778
106779         if (me.showButtons) {
106780             me.okBtn = Ext.create('Ext.button.Button', {
106781                 text: me.okText,
106782                 renderTo: buttonsEl,
106783                 handler: me.onOkClick,
106784                 scope: me
106785             });
106786             me.cancelBtn = Ext.create('Ext.button.Button', {
106787                 text: me.cancelText,
106788                 renderTo: buttonsEl,
106789                 handler: me.onCancelClick,
106790                 scope: me
106791             });
106792         }
106793
106794         me.backRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
106795             handler: Ext.Function.bind(me.adjustYear, me, [-me.totalYears])
106796         });
106797
106798         me.prevEl.addClsOnOver(me.baseCls + '-yearnav-prev-over');
106799         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
106800             handler: Ext.Function.bind(me.adjustYear, me, [me.totalYears])
106801         });
106802         me.nextEl.addClsOnOver(me.baseCls + '-yearnav-next-over');
106803         me.updateBody();
106804     },
106805
106806     /**
106807      * Set the value for the picker.
106808      * @param {Date/Number[]} value The value to set. It can be a Date object, where the month/year will be extracted, or
106809      * it can be an array, with the month as the first index and the year as the second.
106810      * @return {Ext.picker.Month} this
106811      */
106812     setValue: function(value){
106813         var me = this,
106814             active = me.activeYear,
106815             offset = me.monthOffset,
106816             year,
106817             index;
106818
106819         if (!value) {
106820             me.value = [null, null];
106821         } else if (Ext.isDate(value)) {
106822             me.value = [value.getMonth(), value.getFullYear()];
106823         } else {
106824             me.value = [value[0], value[1]];
106825         }
106826
106827         if (me.rendered) {
106828             year = me.value[1];
106829             if (year !== null) {
106830                 if ((year < active || year > active + me.yearOffset)) {
106831                     me.activeYear = year - me.yearOffset + 1;
106832                 }
106833             }
106834             me.updateBody();
106835         }
106836
106837         return me;
106838     },
106839
106840     /**
106841      * Gets the selected value. It is returned as an array [month, year]. It may
106842      * be a partial value, for example [null, 2010]. The month is returned as
106843      * 0 based.
106844      * @return {Number[]} The selected value
106845      */
106846     getValue: function(){
106847         return this.value;
106848     },
106849
106850     /**
106851      * Checks whether the picker has a selection
106852      * @return {Boolean} Returns true if both a month and year have been selected
106853      */
106854     hasSelection: function(){
106855         var value = this.value;
106856         return value[0] !== null && value[1] !== null;
106857     },
106858
106859     /**
106860      * Get an array of years to be pushed in the template. It is not in strict
106861      * numerical order because we want to show them in columns.
106862      * @private
106863      * @return {Number[]} An array of years
106864      */
106865     getYears: function(){
106866         var me = this,
106867             offset = me.yearOffset,
106868             start = me.activeYear, // put the "active" year on the left
106869             end = start + offset,
106870             i = start,
106871             years = [];
106872
106873         for (; i < end; ++i) {
106874             years.push(i, i + offset);
106875         }
106876
106877         return years;
106878     },
106879
106880     /**
106881      * Update the years in the body based on any change
106882      * @private
106883      */
106884     updateBody: function(){
106885         var me = this,
106886             years = me.years,
106887             months = me.months,
106888             yearNumbers = me.getYears(),
106889             cls = me.selectedCls,
106890             value = me.getYear(null),
106891             month = me.value[0],
106892             monthOffset = me.monthOffset,
106893             year;
106894
106895         if (me.rendered) {
106896             years.removeCls(cls);
106897             months.removeCls(cls);
106898             years.each(function(el, all, index){
106899                 year = yearNumbers[index];
106900                 el.dom.innerHTML = year;
106901                 if (year == value) {
106902                     el.dom.className = cls;
106903                 }
106904             });
106905             if (month !== null) {
106906                 if (month < monthOffset) {
106907                     month = month * 2;
106908                 } else {
106909                     month = (month - monthOffset) * 2 + 1;
106910                 }
106911                 months.item(month).addCls(cls);
106912             }
106913         }
106914     },
106915
106916     /**
106917      * Gets the current year value, or the default.
106918      * @private
106919      * @param {Number} defaultValue The default value to use if the year is not defined.
106920      * @param {Number} offset A number to offset the value by
106921      * @return {Number} The year value
106922      */
106923     getYear: function(defaultValue, offset) {
106924         var year = this.value[1];
106925         offset = offset || 0;
106926         return year === null ? defaultValue : year + offset;
106927     },
106928
106929     /**
106930      * React to clicks on the body
106931      * @private
106932      */
106933     onBodyClick: function(e, t) {
106934         var me = this,
106935             isDouble = e.type == 'dblclick';
106936
106937         if (e.getTarget('.' + me.baseCls + '-month')) {
106938             e.stopEvent();
106939             me.onMonthClick(t, isDouble);
106940         } else if (e.getTarget('.' + me.baseCls + '-year')) {
106941             e.stopEvent();
106942             me.onYearClick(t, isDouble);
106943         }
106944     },
106945
106946     /**
106947      * Modify the year display by passing an offset.
106948      * @param {Number} [offset=10] The offset to move by.
106949      */
106950     adjustYear: function(offset){
106951         if (typeof offset != 'number') {
106952             offset = this.totalYears;
106953         }
106954         this.activeYear += offset;
106955         this.updateBody();
106956     },
106957
106958     /**
106959      * React to the ok button being pressed
106960      * @private
106961      */
106962     onOkClick: function(){
106963         this.fireEvent('okclick', this, this.value);
106964     },
106965
106966     /**
106967      * React to the cancel button being pressed
106968      * @private
106969      */
106970     onCancelClick: function(){
106971         this.fireEvent('cancelclick', this);
106972     },
106973
106974     /**
106975      * React to a month being clicked
106976      * @private
106977      * @param {HTMLElement} target The element that was clicked
106978      * @param {Boolean} isDouble True if the event was a doubleclick
106979      */
106980     onMonthClick: function(target, isDouble){
106981         var me = this;
106982         me.value[0] = me.resolveOffset(me.months.indexOf(target), me.monthOffset);
106983         me.updateBody();
106984         me.fireEvent('month' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
106985         me.fireEvent('select', me, me.value);
106986     },
106987
106988     /**
106989      * React to a year being clicked
106990      * @private
106991      * @param {HTMLElement} target The element that was clicked
106992      * @param {Boolean} isDouble True if the event was a doubleclick
106993      */
106994     onYearClick: function(target, isDouble){
106995         var me = this;
106996         me.value[1] = me.activeYear + me.resolveOffset(me.years.indexOf(target), me.yearOffset);
106997         me.updateBody();
106998         me.fireEvent('year' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
106999         me.fireEvent('select', me, me.value);
107000
107001     },
107002
107003     /**
107004      * Returns an offsetted number based on the position in the collection. Since our collections aren't
107005      * numerically ordered, this function helps to normalize those differences.
107006      * @private
107007      * @param {Object} index
107008      * @param {Object} offset
107009      * @return {Number} The correctly offsetted number
107010      */
107011     resolveOffset: function(index, offset){
107012         if (index % 2 === 0) {
107013             return (index / 2);
107014         } else {
107015             return offset + Math.floor(index / 2);
107016         }
107017     },
107018
107019     // private, inherit docs
107020     beforeDestroy: function(){
107021         var me = this;
107022         me.years = me.months = null;
107023         Ext.destroyMembers(me, 'backRepeater', 'nextRepeater', 'okBtn', 'cancelBtn');
107024         me.callParent();
107025     }
107026 });
107027
107028 /**
107029  * A date picker. This class is used by the Ext.form.field.Date field to allow browsing and selection of valid
107030  * dates in a popup next to the field, but may also be used with other components.
107031  *
107032  * Typically you will need to implement a handler function to be notified when the user chooses a date from the picker;
107033  * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
107034  *
107035  * By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate},
107036  * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.
107037  *
107038  * All the string values documented below may be overridden by including an Ext locale file in your page.
107039  *
107040  *     @example
107041  *     Ext.create('Ext.panel.Panel', {
107042  *         title: 'Choose a future date:',
107043  *         width: 200,
107044  *         bodyPadding: 10,
107045  *         renderTo: Ext.getBody(),
107046  *         items: [{
107047  *             xtype: 'datepicker',
107048  *             minDate: new Date(),
107049  *             handler: function(picker, date) {
107050  *                 // do something with the selected date
107051  *             }
107052  *         }]
107053  *     });
107054  */
107055 Ext.define('Ext.picker.Date', {
107056     extend: 'Ext.Component',
107057     requires: [
107058         'Ext.XTemplate',
107059         'Ext.button.Button',
107060         'Ext.button.Split',
107061         'Ext.util.ClickRepeater',
107062         'Ext.util.KeyNav',
107063         'Ext.EventObject',
107064         'Ext.fx.Manager',
107065         'Ext.picker.Month'
107066     ],
107067     alias: 'widget.datepicker',
107068     alternateClassName: 'Ext.DatePicker',
107069
107070     renderTpl: [
107071         '<div class="{cls}" id="{id}" role="grid" title="{ariaTitle} {value:this.longDay}">',
107072             '<div role="presentation" class="{baseCls}-header">',
107073                 '<div class="{baseCls}-prev"><a id="{id}-prevEl" href="#" role="button" title="{prevText}"></a></div>',
107074                 '<div class="{baseCls}-month" id="{id}-middleBtnEl"></div>',
107075                 '<div class="{baseCls}-next"><a id="{id}-nextEl" href="#" role="button" title="{nextText}"></a></div>',
107076             '</div>',
107077             '<table id="{id}-eventEl" class="{baseCls}-inner" cellspacing="0" role="presentation">',
107078                 '<thead role="presentation"><tr role="presentation">',
107079                     '<tpl for="dayNames">',
107080                         '<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>',
107081                     '</tpl>',
107082                 '</tr></thead>',
107083                 '<tbody role="presentation"><tr role="presentation">',
107084                     '<tpl for="days">',
107085                         '{#:this.isEndOfWeek}',
107086                         '<td role="gridcell" id="{[Ext.id()]}">',
107087                             '<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">',
107088                                 '<em role="presentation"><span role="presentation"></span></em>',
107089                             '</a>',
107090                         '</td>',
107091                     '</tpl>',
107092                 '</tr></tbody>',
107093             '</table>',
107094             '<tpl if="showToday">',
107095                 '<div id="{id}-footerEl" role="presentation" class="{baseCls}-footer"></div>',
107096             '</tpl>',
107097         '</div>',
107098         {
107099             firstInitial: function(value) {
107100                 return value.substr(0,1);
107101             },
107102             isEndOfWeek: function(value) {
107103                 // convert from 1 based index to 0 based
107104                 // by decrementing value once.
107105                 value--;
107106                 var end = value % 7 === 0 && value !== 0;
107107                 return end ? '</tr><tr role="row">' : '';
107108             },
107109             longDay: function(value){
107110                 return Ext.Date.format(value, this.longDayFormat);
107111             }
107112         }
107113     ],
107114
107115     ariaTitle: 'Date Picker',
107116
107117     /**
107118      * @cfg {String} todayText
107119      * The text to display on the button that selects the current date
107120      */
107121     todayText : 'Today',
107122
107123     /**
107124      * @cfg {Function} handler
107125      * Optional. A function that will handle the select event of this picker. The handler is passed the following
107126      * parameters:
107127      *
107128      *   - `picker` : Ext.picker.Date
107129      *
107130      * This Date picker.
107131      *
107132      *   - `date` : Date
107133      *
107134      * The selected date.
107135      */
107136
107137     /**
107138      * @cfg {Object} scope
107139      * The scope (`this` reference) in which the `{@link #handler}` function will be called. Defaults to this
107140      * DatePicker instance.
107141      */
107142
107143     /**
107144      * @cfg {String} todayTip
107145      * A string used to format the message for displaying in a tooltip over the button that selects the current date.
107146      * The `{0}` token in string is replaced by today's date.
107147      */
107148     todayTip : '{0} (Spacebar)',
107149
107150     /**
107151      * @cfg {String} minText
107152      * The error text to display if the minDate validation fails.
107153      */
107154     minText : 'This date is before the minimum date',
107155
107156     /**
107157      * @cfg {String} maxText
107158      * The error text to display if the maxDate validation fails.
107159      */
107160     maxText : 'This date is after the maximum date',
107161
107162     /**
107163      * @cfg {String} format
107164      * The default date format string which can be overriden for localization support. The format must be valid
107165      * according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
107166      */
107167
107168     /**
107169      * @cfg {String} disabledDaysText
107170      * The tooltip to display when the date falls on a disabled day.
107171      */
107172     disabledDaysText : 'Disabled',
107173
107174     /**
107175      * @cfg {String} disabledDatesText
107176      * The tooltip text to display when the date falls on a disabled date.
107177      */
107178     disabledDatesText : 'Disabled',
107179
107180     /**
107181      * @cfg {String[]} monthNames
107182      * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
107183      */
107184
107185     /**
107186      * @cfg {String[]} dayNames
107187      * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
107188      */
107189
107190     /**
107191      * @cfg {String} nextText
107192      * The next month navigation button tooltip
107193      */
107194     nextText : 'Next Month (Control+Right)',
107195
107196     /**
107197      * @cfg {String} prevText
107198      * The previous month navigation button tooltip
107199      */
107200     prevText : 'Previous Month (Control+Left)',
107201
107202     /**
107203      * @cfg {String} monthYearText
107204      * The header month selector tooltip
107205      */
107206     monthYearText : 'Choose a month (Control+Up/Down to move years)',
107207
107208     /**
107209      * @cfg {Number} startDay
107210      * Day index at which the week should begin, 0-based (defaults to Sunday)
107211      */
107212     startDay : 0,
107213
107214     /**
107215      * @cfg {Boolean} showToday
107216      * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar that
107217      * selects the current date.
107218      */
107219     showToday : true,
107220
107221     /**
107222      * @cfg {Date} [minDate=null]
107223      * Minimum allowable date (JavaScript date object)
107224      */
107225
107226     /**
107227      * @cfg {Date} [maxDate=null]
107228      * Maximum allowable date (JavaScript date object)
107229      */
107230
107231     /**
107232      * @cfg {Number[]} [disabledDays=null]
107233      * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday.
107234      */
107235
107236     /**
107237      * @cfg {RegExp} [disabledDatesRE=null]
107238      * JavaScript regular expression used to disable a pattern of dates. The {@link #disabledDates}
107239      * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
107240      * disabledDates value.
107241      */
107242
107243     /**
107244      * @cfg {String[]} disabledDates
107245      * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular expression so
107246      * they are very powerful. Some examples:
107247      *
107248      *   - ['03/08/2003', '09/16/2003'] would disable those exact dates
107249      *   - ['03/08', '09/16'] would disable those days for every year
107250      *   - ['^03/08'] would only match the beginning (useful if you are using short years)
107251      *   - ['03/../2006'] would disable every day in March 2006
107252      *   - ['^03'] would disable every day in every March
107253      *
107254      * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
107255      * to support regular expressions, if you are using a date format that has '.' in it, you will have to escape the
107256      * dot when restricting dates. For example: ['03\\.08\\.03'].
107257      */
107258
107259     /**
107260      * @cfg {Boolean} disableAnim
107261      * True to disable animations when showing the month picker.
107262      */
107263     disableAnim: false,
107264
107265     /**
107266      * @cfg {String} [baseCls='x-datepicker']
107267      * The base CSS class to apply to this components element.
107268      */
107269     baseCls: Ext.baseCSSPrefix + 'datepicker',
107270
107271     /**
107272      * @cfg {String} [selectedCls='x-datepicker-selected']
107273      * The class to apply to the selected cell.
107274      */
107275
107276     /**
107277      * @cfg {String} [disabledCellCls='x-datepicker-disabled']
107278      * The class to apply to disabled cells.
107279      */
107280
107281     /**
107282      * @cfg {String} longDayFormat
107283      * The format for displaying a date in a longer format.
107284      */
107285     longDayFormat: 'F d, Y',
107286
107287     /**
107288      * @cfg {Object} keyNavConfig
107289      * Specifies optional custom key event handlers for the {@link Ext.util.KeyNav} attached to this date picker. Must
107290      * conform to the config format recognized by the {@link Ext.util.KeyNav} constructor. Handlers specified in this
107291      * object will replace default handlers of the same name.
107292      */
107293
107294     /**
107295      * @cfg {Boolean} focusOnShow
107296      * True to automatically focus the picker on show.
107297      */
107298     focusOnShow: false,
107299
107300     // private
107301     // Set by other components to stop the picker focus being updated when the value changes.
107302     focusOnSelect: true,
107303
107304     width: 178,
107305
107306     // default value used to initialise each date in the DatePicker
107307     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
107308     initHour: 12, // 24-hour format
107309
107310     numDays: 42,
107311
107312     // private, inherit docs
107313     initComponent : function() {
107314         var me = this,
107315             clearTime = Ext.Date.clearTime;
107316
107317         me.selectedCls = me.baseCls + '-selected';
107318         me.disabledCellCls = me.baseCls + '-disabled';
107319         me.prevCls = me.baseCls + '-prevday';
107320         me.activeCls = me.baseCls + '-active';
107321         me.nextCls = me.baseCls + '-prevday';
107322         me.todayCls = me.baseCls + '-today';
107323         me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
107324         this.callParent();
107325
107326         me.value = me.value ?
107327                  clearTime(me.value, true) : clearTime(new Date());
107328
107329         me.addEvents(
107330             /**
107331              * @event select
107332              * Fires when a date is selected
107333              * @param {Ext.picker.Date} this DatePicker
107334              * @param {Date} date The selected date
107335              */
107336             'select'
107337         );
107338
107339         me.initDisabledDays();
107340     },
107341
107342     // private, inherit docs
107343     onRender : function(container, position){
107344         /*
107345          * days array for looping through 6 full weeks (6 weeks * 7 days)
107346          * Note that we explicitly force the size here so the template creates
107347          * all the appropriate cells.
107348          */
107349
107350         var me = this,
107351             days = new Array(me.numDays),
107352             today = Ext.Date.format(new Date(), me.format);
107353
107354         Ext.applyIf(me, {
107355             renderData: {}
107356         });
107357
107358         Ext.apply(me.renderData, {
107359             dayNames: me.dayNames,
107360             ariaTitle: me.ariaTitle,
107361             value: me.value,
107362             showToday: me.showToday,
107363             prevText: me.prevText,
107364             nextText: me.nextText,
107365             days: days
107366         });
107367         me.getTpl('renderTpl').longDayFormat = me.longDayFormat;
107368
107369         me.addChildEls('eventEl', 'prevEl', 'nextEl', 'middleBtnEl', 'footerEl');
107370
107371         this.callParent(arguments);
107372         me.el.unselectable();
107373
107374         me.cells = me.eventEl.select('tbody td');
107375         me.textNodes = me.eventEl.query('tbody td span');
107376
107377         me.monthBtn = Ext.create('Ext.button.Split', {
107378             text: '',
107379             tooltip: me.monthYearText,
107380             renderTo: me.middleBtnEl
107381         });
107382         //~ me.middleBtnEl.down('button').addCls(Ext.baseCSSPrefix + 'btn-arrow');
107383
107384
107385         me.todayBtn = Ext.create('Ext.button.Button', {
107386             renderTo: me.footerEl,
107387             text: Ext.String.format(me.todayText, today),
107388             tooltip: Ext.String.format(me.todayTip, today),
107389             handler: me.selectToday,
107390             scope: me
107391         });
107392     },
107393
107394     // private, inherit docs
107395     initEvents: function(){
107396         var me = this,
107397             eDate = Ext.Date,
107398             day = eDate.DAY;
107399
107400         this.callParent();
107401
107402         me.prevRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
107403             handler: me.showPrevMonth,
107404             scope: me,
107405             preventDefault: true,
107406             stopDefault: true
107407         });
107408
107409         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
107410             handler: me.showNextMonth,
107411             scope: me,
107412             preventDefault:true,
107413             stopDefault:true
107414         });
107415
107416         me.keyNav = Ext.create('Ext.util.KeyNav', me.eventEl, Ext.apply({
107417             scope: me,
107418             'left' : function(e){
107419                 if(e.ctrlKey){
107420                     me.showPrevMonth();
107421                 }else{
107422                     me.update(eDate.add(me.activeDate, day, -1));
107423                 }
107424             },
107425
107426             'right' : function(e){
107427                 if(e.ctrlKey){
107428                     me.showNextMonth();
107429                 }else{
107430                     me.update(eDate.add(me.activeDate, day, 1));
107431                 }
107432             },
107433
107434             'up' : function(e){
107435                 if(e.ctrlKey){
107436                     me.showNextYear();
107437                 }else{
107438                     me.update(eDate.add(me.activeDate, day, -7));
107439                 }
107440             },
107441
107442             'down' : function(e){
107443                 if(e.ctrlKey){
107444                     me.showPrevYear();
107445                 }else{
107446                     me.update(eDate.add(me.activeDate, day, 7));
107447                 }
107448             },
107449             'pageUp' : me.showNextMonth,
107450             'pageDown' : me.showPrevMonth,
107451             'enter' : function(e){
107452                 e.stopPropagation();
107453                 return true;
107454             }
107455         }, me.keyNavConfig));
107456
107457         if(me.showToday){
107458             me.todayKeyListener = me.eventEl.addKeyListener(Ext.EventObject.SPACE, me.selectToday,  me);
107459         }
107460         me.mon(me.eventEl, 'mousewheel', me.handleMouseWheel, me);
107461         me.mon(me.eventEl, 'click', me.handleDateClick,  me, {delegate: 'a.' + me.baseCls + '-date'});
107462         me.mon(me.monthBtn, 'click', me.showMonthPicker, me);
107463         me.mon(me.monthBtn, 'arrowclick', me.showMonthPicker, me);
107464         me.update(me.value);
107465     },
107466
107467     /**
107468      * Setup the disabled dates regex based on config options
107469      * @private
107470      */
107471     initDisabledDays : function(){
107472         var me = this,
107473             dd = me.disabledDates,
107474             re = '(?:',
107475             len;
107476
107477         if(!me.disabledDatesRE && dd){
107478                 len = dd.length - 1;
107479
107480             Ext.each(dd, function(d, i){
107481                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(d, me.format)) + '$' : dd[i];
107482                 if(i != len){
107483                     re += '|';
107484                 }
107485             }, me);
107486             me.disabledDatesRE = new RegExp(re + ')');
107487         }
107488     },
107489
107490     /**
107491      * Replaces any existing disabled dates with new values and refreshes the DatePicker.
107492      * @param {String[]/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config for
107493      * details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
107494      * @return {Ext.picker.Date} this
107495      */
107496     setDisabledDates : function(dd){
107497         var me = this;
107498
107499         if(Ext.isArray(dd)){
107500             me.disabledDates = dd;
107501             me.disabledDatesRE = null;
107502         }else{
107503             me.disabledDatesRE = dd;
107504         }
107505         me.initDisabledDays();
107506         me.update(me.value, true);
107507         return me;
107508     },
107509
107510     /**
107511      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
107512      * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details
107513      * on supported values.
107514      * @return {Ext.picker.Date} this
107515      */
107516     setDisabledDays : function(dd){
107517         this.disabledDays = dd;
107518         return this.update(this.value, true);
107519     },
107520
107521     /**
107522      * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
107523      * @param {Date} value The minimum date that can be selected
107524      * @return {Ext.picker.Date} this
107525      */
107526     setMinDate : function(dt){
107527         this.minDate = dt;
107528         return this.update(this.value, true);
107529     },
107530
107531     /**
107532      * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
107533      * @param {Date} value The maximum date that can be selected
107534      * @return {Ext.picker.Date} this
107535      */
107536     setMaxDate : function(dt){
107537         this.maxDate = dt;
107538         return this.update(this.value, true);
107539     },
107540
107541     /**
107542      * Sets the value of the date field
107543      * @param {Date} value The date to set
107544      * @return {Ext.picker.Date} this
107545      */
107546     setValue : function(value){
107547         this.value = Ext.Date.clearTime(value, true);
107548         return this.update(this.value);
107549     },
107550
107551     /**
107552      * Gets the current selected value of the date field
107553      * @return {Date} The selected date
107554      */
107555     getValue : function(){
107556         return this.value;
107557     },
107558
107559     // private
107560     focus : function(){
107561         this.update(this.activeDate);
107562     },
107563
107564     // private, inherit docs
107565     onEnable: function(){
107566         this.callParent();
107567         this.setDisabledStatus(false);
107568         this.update(this.activeDate);
107569
107570     },
107571
107572     // private, inherit docs
107573     onDisable : function(){
107574         this.callParent();
107575         this.setDisabledStatus(true);
107576     },
107577
107578     /**
107579      * Set the disabled state of various internal components
107580      * @private
107581      * @param {Boolean} disabled
107582      */
107583     setDisabledStatus : function(disabled){
107584         var me = this;
107585
107586         me.keyNav.setDisabled(disabled);
107587         me.prevRepeater.setDisabled(disabled);
107588         me.nextRepeater.setDisabled(disabled);
107589         if (me.showToday) {
107590             me.todayKeyListener.setDisabled(disabled);
107591             me.todayBtn.setDisabled(disabled);
107592         }
107593     },
107594
107595     /**
107596      * Get the current active date.
107597      * @private
107598      * @return {Date} The active date
107599      */
107600     getActive: function(){
107601         return this.activeDate || this.value;
107602     },
107603
107604     /**
107605      * Run any animation required to hide/show the month picker.
107606      * @private
107607      * @param {Boolean} isHide True if it's a hide operation
107608      */
107609     runAnimation: function(isHide){
107610         var picker = this.monthPicker,
107611             options = {
107612                 duration: 200,
107613                 callback: function(){
107614                     if (isHide) {
107615                         picker.hide();
107616                     } else {
107617                         picker.show();
107618                     }
107619                 }
107620             };
107621
107622         if (isHide) {
107623             picker.el.slideOut('t', options);
107624         } else {
107625             picker.el.slideIn('t', options);
107626         }
107627     },
107628
107629     /**
107630      * Hides the month picker, if it's visible.
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     hideMonthPicker : function(animate){
107637         var me = this,
107638             picker = me.monthPicker;
107639
107640         if (picker) {
107641             if (me.shouldAnimate(animate)) {
107642                 me.runAnimation(true);
107643             } else {
107644                 picker.hide();
107645             }
107646         }
107647         return me;
107648     },
107649
107650     /**
107651      * Show the month picker
107652      * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
107653      * parameter is not specified, the behavior will use {@link #disableAnim} to determine
107654      * whether to animate or not.
107655      * @return {Ext.picker.Date} this
107656      */
107657     showMonthPicker : function(animate){
107658         var me = this,
107659             picker;
107660         
107661         if (me.rendered && !me.disabled) {
107662             picker = me.createMonthPicker();
107663             picker.setValue(me.getActive());
107664             picker.setSize(me.getSize());
107665             picker.setPosition(-1, -1);
107666             if (me.shouldAnimate(animate)) {
107667                 me.runAnimation(false);
107668             } else {
107669                 picker.show();
107670             }
107671         }
107672         return me;
107673     },
107674     
107675     /**
107676      * Checks whether a hide/show action should animate
107677      * @private
107678      * @param {Boolean} [animate] A possible animation value
107679      * @return {Boolean} Whether to animate the action
107680      */
107681     shouldAnimate: function(animate){
107682         return Ext.isDefined(animate) ? animate : !this.disableAnim;
107683     },
107684
107685     /**
107686      * Create the month picker instance
107687      * @private
107688      * @return {Ext.picker.Month} picker
107689      */
107690     createMonthPicker: function(){
107691         var me = this,
107692             picker = me.monthPicker;
107693
107694         if (!picker) {
107695             me.monthPicker = picker = Ext.create('Ext.picker.Month', {
107696                 renderTo: me.el,
107697                 floating: true,
107698                 shadow: false,
107699                 small: me.showToday === false,
107700                 listeners: {
107701                     scope: me,
107702                     cancelclick: me.onCancelClick,
107703                     okclick: me.onOkClick,
107704                     yeardblclick: me.onOkClick,
107705                     monthdblclick: me.onOkClick
107706                 }
107707             });
107708             if (!me.disableAnim) {
107709                 // hide the element if we're animating to prevent an initial flicker
107710                 picker.el.setStyle('display', 'none');
107711             }
107712             me.on('beforehide', Ext.Function.bind(me.hideMonthPicker, me, [false]));
107713         }
107714         return picker;
107715     },
107716
107717     /**
107718      * Respond to an ok click on the month picker
107719      * @private
107720      */
107721     onOkClick: function(picker, value){
107722         var me = this,
107723             month = value[0],
107724             year = value[1],
107725             date = new Date(year, month, me.getActive().getDate());
107726
107727         if (date.getMonth() !== month) {
107728             // 'fix' the JS rolling date conversion if needed
107729             date = new Date(year, month, 1).getLastDateOfMonth();
107730         }
107731         me.update(date);
107732         me.hideMonthPicker();
107733     },
107734
107735     /**
107736      * Respond to a cancel click on the month picker
107737      * @private
107738      */
107739     onCancelClick: function(){
107740         this.hideMonthPicker();
107741     },
107742
107743     /**
107744      * Show the previous month.
107745      * @param {Object} e
107746      * @return {Ext.picker.Date} this
107747      */
107748     showPrevMonth : function(e){
107749         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1));
107750     },
107751
107752     /**
107753      * Show the next month.
107754      * @param {Object} e
107755      * @return {Ext.picker.Date} this
107756      */
107757     showNextMonth : function(e){
107758         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1));
107759     },
107760
107761     /**
107762      * Show the previous year.
107763      * @return {Ext.picker.Date} this
107764      */
107765     showPrevYear : function(){
107766         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1));
107767     },
107768
107769     /**
107770      * Show the next year.
107771      * @return {Ext.picker.Date} this
107772      */
107773     showNextYear : function(){
107774         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1));
107775     },
107776
107777     /**
107778      * Respond to the mouse wheel event
107779      * @private
107780      * @param {Ext.EventObject} e
107781      */
107782     handleMouseWheel : function(e){
107783         e.stopEvent();
107784         if(!this.disabled){
107785             var delta = e.getWheelDelta();
107786             if(delta > 0){
107787                 this.showPrevMonth();
107788             } else if(delta < 0){
107789                 this.showNextMonth();
107790             }
107791         }
107792     },
107793
107794     /**
107795      * Respond to a date being clicked in the picker
107796      * @private
107797      * @param {Ext.EventObject} e
107798      * @param {HTMLElement} t
107799      */
107800     handleDateClick : function(e, t){
107801         var me = this,
107802             handler = me.handler;
107803
107804         e.stopEvent();
107805         if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){
107806             me.cancelFocus = me.focusOnSelect === false;
107807             me.setValue(new Date(t.dateValue));
107808             delete me.cancelFocus;
107809             me.fireEvent('select', me, me.value);
107810             if (handler) {
107811                 handler.call(me.scope || me, me, me.value);
107812             }
107813             // event handling is turned off on hide
107814             // when we are using the picker in a field
107815             // therefore onSelect comes AFTER the select
107816             // event.
107817             me.onSelect();
107818         }
107819     },
107820
107821     /**
107822      * Perform any post-select actions
107823      * @private
107824      */
107825     onSelect: function() {
107826         if (this.hideOnSelect) {
107827              this.hide();
107828          }
107829     },
107830
107831     /**
107832      * Sets the current value to today.
107833      * @return {Ext.picker.Date} this
107834      */
107835     selectToday : function(){
107836         var me = this,
107837             btn = me.todayBtn,
107838             handler = me.handler;
107839
107840         if(btn && !btn.disabled){
107841             me.setValue(Ext.Date.clearTime(new Date()));
107842             me.fireEvent('select', me, me.value);
107843             if (handler) {
107844                 handler.call(me.scope || me, me, me.value);
107845             }
107846             me.onSelect();
107847         }
107848         return me;
107849     },
107850
107851     /**
107852      * Update the selected cell
107853      * @private
107854      * @param {Date} date The new date
107855      * @param {Date} active The active date
107856      */
107857     selectedUpdate: function(date, active){
107858         var me = this,
107859             t = date.getTime(),
107860             cells = me.cells,
107861             cls = me.selectedCls;
107862
107863         cells.removeCls(cls);
107864         cells.each(function(c){
107865             if (c.dom.firstChild.dateValue == t) {
107866                 me.el.dom.setAttribute('aria-activedescendent', c.dom.id);
107867                 c.addCls(cls);
107868                 if(me.isVisible() && !me.cancelFocus){
107869                     Ext.fly(c.dom.firstChild).focus(50);
107870                 }
107871                 return false;
107872             }
107873         }, this);
107874     },
107875
107876     /**
107877      * Update the contents of the picker for a new month
107878      * @private
107879      * @param {Date} date The new date
107880      * @param {Date} active The active date
107881      */
107882     fullUpdate: function(date, active){
107883         var me = this,
107884             cells = me.cells.elements,
107885             textNodes = me.textNodes,
107886             disabledCls = me.disabledCellCls,
107887             eDate = Ext.Date,
107888             i = 0,
107889             extraDays = 0,
107890             visible = me.isVisible(),
107891             sel = +eDate.clearTime(date, true),
107892             today = +eDate.clearTime(new Date()),
107893             min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY,
107894             max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY,
107895             ddMatch = me.disabledDatesRE,
107896             ddText = me.disabledDatesText,
107897             ddays = me.disabledDays ? me.disabledDays.join('') : false,
107898             ddaysText = me.disabledDaysText,
107899             format = me.format,
107900             days = eDate.getDaysInMonth(date),
107901             firstOfMonth = eDate.getFirstDateOfMonth(date),
107902             startingPos = firstOfMonth.getDay() - me.startDay,
107903             previousMonth = eDate.add(date, eDate.MONTH, -1),
107904             longDayFormat = me.longDayFormat,
107905             prevStart,
107906             current,
107907             disableToday,
107908             tempDate,
107909             setCellClass,
107910             html,
107911             cls,
107912             formatValue,
107913             value;
107914
107915         if (startingPos < 0) {
107916             startingPos += 7;
107917         }
107918
107919         days += startingPos;
107920         prevStart = eDate.getDaysInMonth(previousMonth) - startingPos;
107921         current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour);
107922
107923         if (me.showToday) {
107924             tempDate = eDate.clearTime(new Date());
107925             disableToday = (tempDate < min || tempDate > max ||
107926                 (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) ||
107927                 (ddays && ddays.indexOf(tempDate.getDay()) != -1));
107928
107929             if (!me.disabled) {
107930                 me.todayBtn.setDisabled(disableToday);
107931                 me.todayKeyListener.setDisabled(disableToday);
107932             }
107933         }
107934
107935         setCellClass = function(cell){
107936             value = +eDate.clearTime(current, true);
107937             cell.title = eDate.format(current, longDayFormat);
107938             // store dateValue number as an expando
107939             cell.firstChild.dateValue = value;
107940             if(value == today){
107941                 cell.className += ' ' + me.todayCls;
107942                 cell.title = me.todayText;
107943             }
107944             if(value == sel){
107945                 cell.className += ' ' + me.selectedCls;
107946                 me.el.dom.setAttribute('aria-activedescendant', cell.id);
107947                 if (visible && me.floating) {
107948                     Ext.fly(cell.firstChild).focus(50);
107949                 }
107950             }
107951             // disabling
107952             if(value < min) {
107953                 cell.className = disabledCls;
107954                 cell.title = me.minText;
107955                 return;
107956             }
107957             if(value > max) {
107958                 cell.className = disabledCls;
107959                 cell.title = me.maxText;
107960                 return;
107961             }
107962             if(ddays){
107963                 if(ddays.indexOf(current.getDay()) != -1){
107964                     cell.title = ddaysText;
107965                     cell.className = disabledCls;
107966                 }
107967             }
107968             if(ddMatch && format){
107969                 formatValue = eDate.dateFormat(current, format);
107970                 if(ddMatch.test(formatValue)){
107971                     cell.title = ddText.replace('%0', formatValue);
107972                     cell.className = disabledCls;
107973                 }
107974             }
107975         };
107976
107977         for(; i < me.numDays; ++i) {
107978             if (i < startingPos) {
107979                 html = (++prevStart);
107980                 cls = me.prevCls;
107981             } else if (i >= days) {
107982                 html = (++extraDays);
107983                 cls = me.nextCls;
107984             } else {
107985                 html = i - startingPos + 1;
107986                 cls = me.activeCls;
107987             }
107988             textNodes[i].innerHTML = html;
107989             cells[i].className = cls;
107990             current.setDate(current.getDate() + 1);
107991             setCellClass(cells[i]);
107992         }
107993
107994         me.monthBtn.setText(me.monthNames[date.getMonth()] + ' ' + date.getFullYear());
107995     },
107996
107997     /**
107998      * Update the contents of the picker
107999      * @private
108000      * @param {Date} date The new date
108001      * @param {Boolean} forceRefresh True to force a full refresh
108002      */
108003     update : function(date, forceRefresh){
108004         var me = this,
108005             active = me.activeDate;
108006
108007         if (me.rendered) {
108008             me.activeDate = date;
108009             if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
108010                 me.selectedUpdate(date, active);
108011             } else {
108012                 me.fullUpdate(date, active);
108013             }
108014         }
108015         return me;
108016     },
108017
108018     // private, inherit docs
108019     beforeDestroy : function() {
108020         var me = this;
108021
108022         if (me.rendered) {
108023             Ext.destroy(
108024                 me.todayKeyListener,
108025                 me.keyNav,
108026                 me.monthPicker,
108027                 me.monthBtn,
108028                 me.nextRepeater,
108029                 me.prevRepeater,
108030                 me.todayBtn
108031             );
108032             delete me.textNodes;
108033             delete me.cells.elements;
108034         }
108035         me.callParent();
108036     },
108037
108038     // private, inherit docs
108039     onShow: function() {
108040         this.callParent(arguments);
108041         if (this.focusOnShow) {
108042             this.focus();
108043         }
108044     }
108045 },
108046
108047 // After dependencies have loaded:
108048 function() {
108049     var proto = this.prototype;
108050
108051     proto.monthNames = Ext.Date.monthNames;
108052
108053     proto.dayNames = Ext.Date.dayNames;
108054
108055     proto.format = Ext.Date.defaultFormat;
108056 });
108057
108058 /**
108059  * @docauthor Jason Johnston <jason@sencha.com>
108060  *
108061  * Provides a date input field with a {@link Ext.picker.Date date picker} dropdown and automatic date
108062  * validation.
108063  *
108064  * This field recognizes and uses the JavaScript Date object as its main {@link #value} type. In addition,
108065  * it recognizes string values which are parsed according to the {@link #format} and/or {@link #altFormats}
108066  * configs. These may be reconfigured to use date formats appropriate for the user's locale.
108067  *
108068  * The field may be limited to a certain range of dates by using the {@link #minValue}, {@link #maxValue},
108069  * {@link #disabledDays}, and {@link #disabledDates} config parameters. These configurations will be used both
108070  * in the field's validation, and in the date picker dropdown by preventing invalid dates from being selected.
108071  *
108072  * # Example usage
108073  *
108074  *     @example
108075  *     Ext.create('Ext.form.Panel', {
108076  *         renderTo: Ext.getBody(),
108077  *         width: 300,
108078  *         bodyPadding: 10,
108079  *         title: 'Dates',
108080  *         items: [{
108081  *             xtype: 'datefield',
108082  *             anchor: '100%',
108083  *             fieldLabel: 'From',
108084  *             name: 'from_date',
108085  *             maxValue: new Date()  // limited to the current date or prior
108086  *         }, {
108087  *             xtype: 'datefield',
108088  *             anchor: '100%',
108089  *             fieldLabel: 'To',
108090  *             name: 'to_date',
108091  *             value: new Date()  // defaults to today
108092  *         }]
108093  *     });
108094  *
108095  * # Date Formats Examples
108096  *
108097  * This example shows a couple of different date format parsing scenarios. Both use custom date format
108098  * configurations; the first one matches the configured `format` while the second matches the `altFormats`.
108099  *
108100  *     @example
108101  *     Ext.create('Ext.form.Panel', {
108102  *         renderTo: Ext.getBody(),
108103  *         width: 300,
108104  *         bodyPadding: 10,
108105  *         title: 'Dates',
108106  *         items: [{
108107  *             xtype: 'datefield',
108108  *             anchor: '100%',
108109  *             fieldLabel: 'Date',
108110  *             name: 'date',
108111  *             // The value matches the format; will be parsed and displayed using that format.
108112  *             format: 'm d Y',
108113  *             value: '2 4 1978'
108114  *         }, {
108115  *             xtype: 'datefield',
108116  *             anchor: '100%',
108117  *             fieldLabel: 'Date',
108118  *             name: 'date',
108119  *             // The value does not match the format, but does match an altFormat; will be parsed
108120  *             // using the altFormat and displayed using the format.
108121  *             format: 'm d Y',
108122  *             altFormats: 'm,d,Y|m.d.Y',
108123  *             value: '2.4.1978'
108124  *         }]
108125  *     });
108126  */
108127 Ext.define('Ext.form.field.Date', {
108128     extend:'Ext.form.field.Picker',
108129     alias: 'widget.datefield',
108130     requires: ['Ext.picker.Date'],
108131     alternateClassName: ['Ext.form.DateField', 'Ext.form.Date'],
108132
108133     /**
108134      * @cfg {String} format
108135      * The default date format string which can be overriden for localization support. The format must be valid
108136      * according to {@link Ext.Date#parse}.
108137      */
108138     format : "m/d/Y",
108139     /**
108140      * @cfg {String} altFormats
108141      * Multiple date formats separated by "|" to try when parsing a user input value and it does not match the defined
108142      * format.
108143      */
108144     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",
108145     /**
108146      * @cfg {String} disabledDaysText
108147      * The tooltip to display when the date falls on a disabled day.
108148      */
108149     disabledDaysText : "Disabled",
108150     /**
108151      * @cfg {String} disabledDatesText
108152      * The tooltip text to display when the date falls on a disabled date.
108153      */
108154     disabledDatesText : "Disabled",
108155     /**
108156      * @cfg {String} minText
108157      * The error text to display when the date in the cell is before {@link #minValue}.
108158      */
108159     minText : "The date in this field must be equal to or after {0}",
108160     /**
108161      * @cfg {String} maxText
108162      * The error text to display when the date in the cell is after {@link #maxValue}.
108163      */
108164     maxText : "The date in this field must be equal to or before {0}",
108165     /**
108166      * @cfg {String} invalidText
108167      * The error text to display when the date in the field is invalid.
108168      */
108169     invalidText : "{0} is not a valid date - it must be in the format {1}",
108170     /**
108171      * @cfg {String} [triggerCls='x-form-date-trigger']
108172      * An additional CSS class used to style the trigger button. The trigger will always get the class 'x-form-trigger'
108173      * and triggerCls will be **appended** if specified (default class displays a calendar icon).
108174      */
108175     triggerCls : Ext.baseCSSPrefix + 'form-date-trigger',
108176     /**
108177      * @cfg {Boolean} showToday
108178      * false to hide the footer area of the Date picker containing the Today button and disable the keyboard handler for
108179      * spacebar that selects the current date.
108180      */
108181     showToday : true,
108182     /**
108183      * @cfg {Date/String} minValue
108184      * The minimum allowed date. Can be either a Javascript date object or a string date in a valid format.
108185      */
108186     /**
108187      * @cfg {Date/String} maxValue
108188      * The maximum allowed date. Can be either a Javascript date object or a string date in a valid format.
108189      */
108190     /**
108191      * @cfg {Number[]} disabledDays
108192      * An array of days to disable, 0 based. Some examples:
108193      *
108194      *     // disable Sunday and Saturday:
108195      *     disabledDays:  [0, 6]
108196      *     // disable weekdays:
108197      *     disabledDays: [1,2,3,4,5]
108198      */
108199     /**
108200      * @cfg {String[]} disabledDates
108201      * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular expression so
108202      * they are very powerful. Some examples:
108203      *
108204      *     // disable these exact dates:
108205      *     disabledDates: ["03/08/2003", "09/16/2003"]
108206      *     // disable these days for every year:
108207      *     disabledDates: ["03/08", "09/16"]
108208      *     // only match the beginning (useful if you are using short years):
108209      *     disabledDates: ["^03/08"]
108210      *     // disable every day in March 2006:
108211      *     disabledDates: ["03/../2006"]
108212      *     // disable every day in every March:
108213      *     disabledDates: ["^03"]
108214      *
108215      * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
108216      * to support regular expressions, if you are using a {@link #format date format} that has "." in it, you will have
108217      * to escape the dot when restricting dates. For example: `["03\\.08\\.03"]`.
108218      */
108219
108220     /**
108221      * @cfg {String} submitFormat
108222      * The date format string which will be submitted to the server. The format must be valid according to {@link
108223      * Ext.Date#parse} (defaults to {@link #format}).
108224      */
108225
108226     // in the absence of a time value, a default value of 12 noon will be used
108227     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
108228     initTime: '12', // 24 hour format
108229
108230     initTimeFormat: 'H',
108231
108232     matchFieldWidth: false,
108233     /**
108234      * @cfg {Number} startDay
108235      * Day index at which the week should begin, 0-based (defaults to Sunday)
108236      */
108237     startDay: 0,
108238
108239     initComponent : function(){
108240         var me = this,
108241             isString = Ext.isString,
108242             min, max;
108243
108244         min = me.minValue;
108245         max = me.maxValue;
108246         if(isString(min)){
108247             me.minValue = me.parseDate(min);
108248         }
108249         if(isString(max)){
108250             me.maxValue = me.parseDate(max);
108251         }
108252         me.disabledDatesRE = null;
108253         me.initDisabledDays();
108254
108255         me.callParent();
108256     },
108257
108258     initValue: function() {
108259         var me = this,
108260             value = me.value;
108261
108262         // If a String value was supplied, try to convert it to a proper Date
108263         if (Ext.isString(value)) {
108264             me.value = me.rawToValue(value);
108265         }
108266
108267         me.callParent();
108268     },
108269
108270     // private
108271     initDisabledDays : function(){
108272         if(this.disabledDates){
108273             var dd = this.disabledDates,
108274                 len = dd.length - 1,
108275                 re = "(?:";
108276
108277             Ext.each(dd, function(d, i){
108278                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(d.dateFormat(this.format)) + '$' : dd[i];
108279                 if (i !== len) {
108280                     re += '|';
108281                 }
108282             }, this);
108283             this.disabledDatesRE = new RegExp(re + ')');
108284         }
108285     },
108286
108287     /**
108288      * Replaces any existing disabled dates with new values and refreshes the Date picker.
108289      * @param {String[]} disabledDates An array of date strings (see the {@link #disabledDates} config for details on
108290      * supported values) used to disable a pattern of dates.
108291      */
108292     setDisabledDates : function(dd){
108293         var me = this,
108294             picker = me.picker;
108295
108296         me.disabledDates = dd;
108297         me.initDisabledDays();
108298         if (picker) {
108299             picker.setDisabledDates(me.disabledDatesRE);
108300         }
108301     },
108302
108303     /**
108304      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the Date picker.
108305      * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details on
108306      * supported values.
108307      */
108308     setDisabledDays : function(dd){
108309         var picker = this.picker;
108310
108311         this.disabledDays = dd;
108312         if (picker) {
108313             picker.setDisabledDays(dd);
108314         }
108315     },
108316
108317     /**
108318      * Replaces any existing {@link #minValue} with the new value and refreshes the Date picker.
108319      * @param {Date} value The minimum date that can be selected
108320      */
108321     setMinValue : function(dt){
108322         var me = this,
108323             picker = me.picker,
108324             minValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
108325
108326         me.minValue = minValue;
108327         if (picker) {
108328             picker.minText = Ext.String.format(me.minText, me.formatDate(me.minValue));
108329             picker.setMinDate(minValue);
108330         }
108331     },
108332
108333     /**
108334      * Replaces any existing {@link #maxValue} with the new value and refreshes the Date picker.
108335      * @param {Date} value The maximum date that can be selected
108336      */
108337     setMaxValue : function(dt){
108338         var me = this,
108339             picker = me.picker,
108340             maxValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
108341
108342         me.maxValue = maxValue;
108343         if (picker) {
108344             picker.maxText = Ext.String.format(me.maxText, me.formatDate(me.maxValue));
108345             picker.setMaxDate(maxValue);
108346         }
108347     },
108348
108349     /**
108350      * Runs all of Date's validations and returns an array of any errors. Note that this first runs Text's validations,
108351      * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
108352      * the date format is valid, that the chosen date is within the min and max date constraints set, that the date
108353      * chosen is not in the disabledDates regex and that the day chosed is not one of the disabledDays.
108354      * @param {Object} [value] The value to get errors for (defaults to the current field value)
108355      * @return {String[]} All validation errors for this field
108356      */
108357     getErrors: function(value) {
108358         var me = this,
108359             format = Ext.String.format,
108360             clearTime = Ext.Date.clearTime,
108361             errors = me.callParent(arguments),
108362             disabledDays = me.disabledDays,
108363             disabledDatesRE = me.disabledDatesRE,
108364             minValue = me.minValue,
108365             maxValue = me.maxValue,
108366             len = disabledDays ? disabledDays.length : 0,
108367             i = 0,
108368             svalue,
108369             fvalue,
108370             day,
108371             time;
108372
108373         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
108374
108375         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
108376              return errors;
108377         }
108378
108379         svalue = value;
108380         value = me.parseDate(value);
108381         if (!value) {
108382             errors.push(format(me.invalidText, svalue, me.format));
108383             return errors;
108384         }
108385
108386         time = value.getTime();
108387         if (minValue && time < clearTime(minValue).getTime()) {
108388             errors.push(format(me.minText, me.formatDate(minValue)));
108389         }
108390
108391         if (maxValue && time > clearTime(maxValue).getTime()) {
108392             errors.push(format(me.maxText, me.formatDate(maxValue)));
108393         }
108394
108395         if (disabledDays) {
108396             day = value.getDay();
108397
108398             for(; i < len; i++) {
108399                 if (day === disabledDays[i]) {
108400                     errors.push(me.disabledDaysText);
108401                     break;
108402                 }
108403             }
108404         }
108405
108406         fvalue = me.formatDate(value);
108407         if (disabledDatesRE && disabledDatesRE.test(fvalue)) {
108408             errors.push(format(me.disabledDatesText, fvalue));
108409         }
108410
108411         return errors;
108412     },
108413
108414     rawToValue: function(rawValue) {
108415         return this.parseDate(rawValue) || rawValue || null;
108416     },
108417
108418     valueToRaw: function(value) {
108419         return this.formatDate(this.parseDate(value));
108420     },
108421
108422     /**
108423      * @method setValue
108424      * Sets the value of the date field. You can pass a date object or any string that can be parsed into a valid date,
108425      * using {@link #format} as the date format, according to the same rules as {@link Ext.Date#parse} (the default
108426      * format used is "m/d/Y").
108427      *
108428      * Usage:
108429      *
108430      *     //All of these calls set the same date value (May 4, 2006)
108431      *
108432      *     //Pass a date object:
108433      *     var dt = new Date('5/4/2006');
108434      *     dateField.setValue(dt);
108435      *
108436      *     //Pass a date string (default format):
108437      *     dateField.setValue('05/04/2006');
108438      *
108439      *     //Pass a date string (custom format):
108440      *     dateField.format = 'Y-m-d';
108441      *     dateField.setValue('2006-05-04');
108442      *
108443      * @param {String/Date} date The date or valid date string
108444      * @return {Ext.form.field.Date} this
108445      */
108446
108447     /**
108448      * Attempts to parse a given string value using a given {@link Ext.Date#parse date format}.
108449      * @param {String} value The value to attempt to parse
108450      * @param {String} format A valid date format (see {@link Ext.Date#parse})
108451      * @return {Date} The parsed Date object, or null if the value could not be successfully parsed.
108452      */
108453     safeParse : function(value, format) {
108454         var me = this,
108455             utilDate = Ext.Date,
108456             parsedDate,
108457             result = null;
108458
108459         if (utilDate.formatContainsHourInfo(format)) {
108460             // if parse format contains hour information, no DST adjustment is necessary
108461             result = utilDate.parse(value, format);
108462         } else {
108463             // set time to 12 noon, then clear the time
108464             parsedDate = utilDate.parse(value + ' ' + me.initTime, format + ' ' + me.initTimeFormat);
108465             if (parsedDate) {
108466                 result = utilDate.clearTime(parsedDate);
108467             }
108468         }
108469         return result;
108470     },
108471
108472     // @private
108473     getSubmitValue: function() {
108474         var format = this.submitFormat || this.format,
108475             value = this.getValue();
108476
108477         return value ? Ext.Date.format(value, format) : '';
108478     },
108479
108480     /**
108481      * @private
108482      */
108483     parseDate : function(value) {
108484         if(!value || Ext.isDate(value)){
108485             return value;
108486         }
108487
108488         var me = this,
108489             val = me.safeParse(value, me.format),
108490             altFormats = me.altFormats,
108491             altFormatsArray = me.altFormatsArray,
108492             i = 0,
108493             len;
108494
108495         if (!val && altFormats) {
108496             altFormatsArray = altFormatsArray || altFormats.split('|');
108497             len = altFormatsArray.length;
108498             for (; i < len && !val; ++i) {
108499                 val = me.safeParse(value, altFormatsArray[i]);
108500             }
108501         }
108502         return val;
108503     },
108504
108505     // private
108506     formatDate : function(date){
108507         return Ext.isDate(date) ? Ext.Date.dateFormat(date, this.format) : date;
108508     },
108509
108510     createPicker: function() {
108511         var me = this,
108512             format = Ext.String.format;
108513
108514         return Ext.create('Ext.picker.Date', {
108515             pickerField: me,
108516             ownerCt: me.ownerCt,
108517             renderTo: document.body,
108518             floating: true,
108519             hidden: true,
108520             focusOnShow: true,
108521             minDate: me.minValue,
108522             maxDate: me.maxValue,
108523             disabledDatesRE: me.disabledDatesRE,
108524             disabledDatesText: me.disabledDatesText,
108525             disabledDays: me.disabledDays,
108526             disabledDaysText: me.disabledDaysText,
108527             format: me.format,
108528             showToday: me.showToday,
108529             startDay: me.startDay,
108530             minText: format(me.minText, me.formatDate(me.minValue)),
108531             maxText: format(me.maxText, me.formatDate(me.maxValue)),
108532             listeners: {
108533                 scope: me,
108534                 select: me.onSelect
108535             },
108536             keyNavConfig: {
108537                 esc: function() {
108538                     me.collapse();
108539                 }
108540             }
108541         });
108542     },
108543
108544     onSelect: function(m, d) {
108545         var me = this;
108546
108547         me.setValue(d);
108548         me.fireEvent('select', me, d);
108549         me.collapse();
108550     },
108551
108552     /**
108553      * @private
108554      * Sets the Date picker's value to match the current field value when expanding.
108555      */
108556     onExpand: function() {
108557         var value = this.getValue();
108558         this.picker.setValue(Ext.isDate(value) ? value : new Date());
108559     },
108560
108561     /**
108562      * @private
108563      * Focuses the field when collapsing the Date picker.
108564      */
108565     onCollapse: function() {
108566         this.focus(false, 60);
108567     },
108568
108569     // private
108570     beforeBlur : function(){
108571         var me = this,
108572             v = me.parseDate(me.getRawValue()),
108573             focusTask = me.focusTask;
108574
108575         if (focusTask) {
108576             focusTask.cancel();
108577         }
108578
108579         if (v) {
108580             me.setValue(v);
108581         }
108582     }
108583
108584     /**
108585      * @hide
108586      * @cfg {Boolean} grow
108587      */
108588     /**
108589      * @hide
108590      * @cfg {Number} growMin
108591      */
108592     /**
108593      * @hide
108594      * @cfg {Number} growMax
108595      */
108596     /**
108597      * @hide
108598      * @method autoSize
108599      */
108600 });
108601
108602 /**
108603  * A display-only text field which is not validated and not submitted. This is useful for when you want to display a
108604  * 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
108605  * value. The value can be optionally {@link #htmlEncode HTML encoded} if it contains HTML markup that you do not want
108606  * to be rendered.
108607  *
108608  * If you have more complex content, or need to include components within the displayed content, also consider using a
108609  * {@link Ext.form.FieldContainer} instead.
108610  *
108611  * Example:
108612  *
108613  *     @example
108614  *     Ext.create('Ext.form.Panel', {
108615  *         renderTo: Ext.getBody(),
108616  *         width: 175,
108617  *         height: 120,
108618  *         bodyPadding: 10,
108619  *         title: 'Final Score',
108620  *         items: [{
108621  *             xtype: 'displayfield',
108622  *             fieldLabel: 'Home',
108623  *             name: 'home_score',
108624  *             value: '10'
108625  *         }, {
108626  *             xtype: 'displayfield',
108627  *             fieldLabel: 'Visitor',
108628  *             name: 'visitor_score',
108629  *             value: '11'
108630  *         }],
108631  *         buttons: [{
108632  *             text: 'Update',
108633  *         }]
108634  *     });
108635  */
108636 Ext.define('Ext.form.field.Display', {
108637     extend:'Ext.form.field.Base',
108638     alias: 'widget.displayfield',
108639     requires: ['Ext.util.Format', 'Ext.XTemplate'],
108640     alternateClassName: ['Ext.form.DisplayField', 'Ext.form.Display'],
108641     fieldSubTpl: [
108642         '<div id="{id}" class="{fieldCls}"></div>',
108643         {
108644             compiled: true,
108645             disableFormats: true
108646         }
108647     ],
108648
108649     /**
108650      * @cfg {String} [fieldCls="x-form-display-field"]
108651      * The default CSS class for the field.
108652      */
108653     fieldCls: Ext.baseCSSPrefix + 'form-display-field',
108654
108655     /**
108656      * @cfg {Boolean} htmlEncode
108657      * false to skip HTML-encoding the text when rendering it. This might be useful if you want to
108658      * include tags in the field's innerHTML rather than rendering them as string literals per the default logic.
108659      */
108660     htmlEncode: false,
108661
108662     validateOnChange: false,
108663
108664     initEvents: Ext.emptyFn,
108665
108666     submitValue: false,
108667
108668     isValid: function() {
108669         return true;
108670     },
108671
108672     validate: function() {
108673         return true;
108674     },
108675
108676     getRawValue: function() {
108677         return this.rawValue;
108678     },
108679
108680     setRawValue: function(value) {
108681         var me = this;
108682         value = Ext.value(value, '');
108683         me.rawValue = value;
108684         if (me.rendered) {
108685             me.inputEl.dom.innerHTML = me.htmlEncode ? Ext.util.Format.htmlEncode(value) : value;
108686         }
108687         return value;
108688     },
108689
108690     // private
108691     getContentTarget: function() {
108692         return this.inputEl;
108693     }
108694
108695     /**
108696      * @cfg {String} inputType
108697      * @hide
108698      */
108699     /**
108700      * @cfg {Boolean} disabled
108701      * @hide
108702      */
108703     /**
108704      * @cfg {Boolean} readOnly
108705      * @hide
108706      */
108707     /**
108708      * @cfg {Boolean} validateOnChange
108709      * @hide
108710      */
108711     /**
108712      * @cfg {Number} checkChangeEvents
108713      * @hide
108714      */
108715     /**
108716      * @cfg {Number} checkChangeBuffer
108717      * @hide
108718      */
108719 });
108720
108721 /**
108722  * @docauthor Jason Johnston <jason@sencha.com>
108723  *
108724  * A file upload field which has custom styling and allows control over the button text and other
108725  * features of {@link Ext.form.field.Text text fields} like {@link Ext.form.field.Text#emptyText empty text}.
108726  * It uses a hidden file input element behind the scenes to allow user selection of a file and to
108727  * perform the actual upload during {@link Ext.form.Basic#submit form submit}.
108728  *
108729  * Because there is no secure cross-browser way to programmatically set the value of a file input,
108730  * the standard Field `setValue` method is not implemented. The `{@link #getValue}` method will return
108731  * a value that is browser-dependent; some have just the file name, some have a full path, some use
108732  * a fake path.
108733  *
108734  * **IMPORTANT:** File uploads are not performed using normal 'Ajax' techniques; see the description for
108735  * {@link Ext.form.Basic#hasUpload} for details.
108736  *
108737  * # Example Usage
108738  *
108739  *     @example
108740  *     Ext.create('Ext.form.Panel', {
108741  *         title: 'Upload a Photo',
108742  *         width: 400,
108743  *         bodyPadding: 10,
108744  *         frame: true,
108745  *         renderTo: Ext.getBody(),
108746  *         items: [{
108747  *             xtype: 'filefield',
108748  *             name: 'photo',
108749  *             fieldLabel: 'Photo',
108750  *             labelWidth: 50,
108751  *             msgTarget: 'side',
108752  *             allowBlank: false,
108753  *             anchor: '100%',
108754  *             buttonText: 'Select Photo...'
108755  *         }],
108756  *
108757  *         buttons: [{
108758  *             text: 'Upload',
108759  *             handler: function() {
108760  *                 var form = this.up('form').getForm();
108761  *                 if(form.isValid()){
108762  *                     form.submit({
108763  *                         url: 'photo-upload.php',
108764  *                         waitMsg: 'Uploading your photo...',
108765  *                         success: function(fp, o) {
108766  *                             Ext.Msg.alert('Success', 'Your photo "' + o.result.file + '" has been uploaded.');
108767  *                         }
108768  *                     });
108769  *                 }
108770  *             }
108771  *         }]
108772  *     });
108773  */
108774 Ext.define("Ext.form.field.File", {
108775     extend: 'Ext.form.field.Text',
108776     alias: ['widget.filefield', 'widget.fileuploadfield'],
108777     alternateClassName: ['Ext.form.FileUploadField', 'Ext.ux.form.FileUploadField', 'Ext.form.File'],
108778     uses: ['Ext.button.Button', 'Ext.layout.component.field.File'],
108779
108780     /**
108781      * @cfg {String} buttonText
108782      * The button text to display on the upload button. Note that if you supply a value for
108783      * {@link #buttonConfig}, the buttonConfig.text value will be used instead if available.
108784      */
108785     buttonText: 'Browse...',
108786
108787     /**
108788      * @cfg {Boolean} buttonOnly
108789      * True to display the file upload field as a button with no visible text field. If true, all
108790      * inherited Text members will still be available.
108791      */
108792     buttonOnly: false,
108793
108794     /**
108795      * @cfg {Number} buttonMargin
108796      * The number of pixels of space reserved between the button and the text field. Note that this only
108797      * applies if {@link #buttonOnly} = false.
108798      */
108799     buttonMargin: 3,
108800
108801     /**
108802      * @cfg {Object} buttonConfig
108803      * A standard {@link Ext.button.Button} config object.
108804      */
108805
108806     /**
108807      * @event change
108808      * Fires when the underlying file input field's value has changed from the user selecting a new file from the system
108809      * file selection dialog.
108810      * @param {Ext.ux.form.FileUploadField} this
108811      * @param {String} value The file value returned by the underlying file input field
108812      */
108813
108814     /**
108815      * @property {Ext.Element} fileInputEl
108816      * A reference to the invisible file input element created for this upload field. Only populated after this
108817      * component is rendered.
108818      */
108819
108820     /**
108821      * @property {Ext.button.Button} button
108822      * A reference to the trigger Button component created for this upload field. Only populated after this component is
108823      * rendered.
108824      */
108825
108826     /**
108827      * @cfg {String} [fieldBodyCls='x-form-file-wrap']
108828      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
108829      */
108830     fieldBodyCls: Ext.baseCSSPrefix + 'form-file-wrap',
108831
108832     /**
108833      * @cfg {Boolean} readOnly
108834      * Unlike with other form fields, the readOnly config defaults to true in File field.
108835      */
108836     readOnly: true,
108837
108838     // private
108839     componentLayout: 'filefield',
108840
108841     // private
108842     onRender: function() {
108843         var me = this,
108844             inputEl;
108845
108846         me.callParent(arguments);
108847
108848         me.createButton();
108849         me.createFileInput();
108850         
108851         // we don't create the file/button til after onRender, the initial disable() is
108852         // called in the onRender of the component.
108853         if (me.disabled) {
108854             me.disableItems();
108855         }
108856
108857         inputEl = me.inputEl;
108858         inputEl.dom.removeAttribute('name'); //name goes on the fileInput, not the text input
108859         if (me.buttonOnly) {
108860             inputEl.setDisplayed(false);
108861         }
108862     },
108863
108864     /**
108865      * @private
108866      * Creates the custom trigger Button component. The fileInput will be inserted into this.
108867      */
108868     createButton: function() {
108869         var me = this;
108870         me.button = Ext.widget('button', Ext.apply({
108871             ui: me.ui,
108872             renderTo: me.bodyEl,
108873             text: me.buttonText,
108874             cls: Ext.baseCSSPrefix + 'form-file-btn',
108875             preventDefault: false,
108876             style: me.buttonOnly ? '' : 'margin-left:' + me.buttonMargin + 'px'
108877         }, me.buttonConfig));
108878     },
108879
108880     /**
108881      * @private
108882      * Creates the file input element. It is inserted into the trigger button component, made
108883      * invisible, and floated on top of the button's other content so that it will receive the
108884      * button's clicks.
108885      */
108886     createFileInput : function() {
108887         var me = this;
108888         me.fileInputEl = me.button.el.createChild({
108889             name: me.getName(),
108890             cls: Ext.baseCSSPrefix + 'form-file-input',
108891             tag: 'input',
108892             type: 'file',
108893             size: 1
108894         }).on('change', me.onFileChange, me);
108895     },
108896
108897     /**
108898      * @private Event handler fired when the user selects a file.
108899      */
108900     onFileChange: function() {
108901         this.lastValue = null; // force change event to get fired even if the user selects a file with the same name
108902         Ext.form.field.File.superclass.setValue.call(this, this.fileInputEl.dom.value);
108903     },
108904
108905     /**
108906      * Overridden to do nothing
108907      * @hide
108908      */
108909     setValue: Ext.emptyFn,
108910
108911     reset : function(){
108912         var me = this;
108913         if (me.rendered) {
108914             me.fileInputEl.remove();
108915             me.createFileInput();
108916             me.inputEl.dom.value = '';
108917         }
108918         me.callParent();
108919     },
108920
108921     onDisable: function(){
108922         this.callParent();
108923         this.disableItems();
108924     },
108925     
108926     disableItems: function(){
108927         var file = this.fileInputEl,
108928             button = this.button;
108929              
108930         if (file) {
108931             file.dom.disabled = true;
108932         }
108933         if (button) {
108934             button.disable();
108935         }    
108936     },
108937
108938     onEnable: function(){
108939         var me = this;
108940         me.callParent();
108941         me.fileInputEl.dom.disabled = false;
108942         me.button.enable();
108943     },
108944
108945     isFileUpload: function() {
108946         return true;
108947     },
108948
108949     extractFileInput: function() {
108950         var fileInput = this.fileInputEl.dom;
108951         this.reset();
108952         return fileInput;
108953     },
108954
108955     onDestroy: function(){
108956         Ext.destroyMembers(this, 'fileInputEl', 'button');
108957         this.callParent();
108958     }
108959
108960
108961 });
108962
108963 /**
108964  * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
108965  *
108966  * This creates an actual input element with type="submit" in the DOM. While its label is
108967  * {@link #hideLabel not rendered} by default, it is still a real component and may be sized according
108968  * to its owner container's layout.
108969  *
108970  * Because of this, in most cases it is more convenient and less problematic to simply
108971  * {@link Ext.form.action.Action#params pass hidden parameters} directly when
108972  * {@link Ext.form.Basic#submit submitting the form}.
108973  *
108974  * Example:
108975  *
108976  *     new Ext.form.Panel({
108977  *         title: 'My Form',
108978  *         items: [{
108979  *             xtype: 'textfield',
108980  *             fieldLabel: 'Text Field',
108981  *             name: 'text_field',
108982  *             value: 'value from text field'
108983  *         }, {
108984  *             xtype: 'hiddenfield',
108985  *             name: 'hidden_field_1',
108986  *             value: 'value from hidden field'
108987  *         }],
108988  *
108989  *         buttons: [{
108990  *             text: 'Submit',
108991  *             handler: function() {
108992  *                 this.up('form').getForm().submit({
108993  *                     params: {
108994  *                         hidden_field_2: 'value from submit call'
108995  *                     }
108996  *                 });
108997  *             }
108998  *         }]
108999  *     });
109000  *
109001  * Submitting the above form will result in three values sent to the server:
109002  *
109003  *     text_field=value+from+text+field&hidden;_field_1=value+from+hidden+field&hidden_field_2=value+from+submit+call
109004  *
109005  */
109006 Ext.define('Ext.form.field.Hidden', {
109007     extend:'Ext.form.field.Base',
109008     alias: ['widget.hiddenfield', 'widget.hidden'],
109009     alternateClassName: 'Ext.form.Hidden',
109010
109011     // private
109012     inputType : 'hidden',
109013     hideLabel: true,
109014     
109015     initComponent: function(){
109016         this.formItemCls += '-hidden';
109017         this.callParent();    
109018     },
109019     
109020     /**
109021      * @private
109022      * Override. Treat undefined and null values as equal to an empty string value.
109023      */
109024     isEqual: function(value1, value2) {
109025         return this.isEqualAsString(value1, value2);
109026     },
109027
109028     // These are all private overrides
109029     initEvents: Ext.emptyFn,
109030     setSize : Ext.emptyFn,
109031     setWidth : Ext.emptyFn,
109032     setHeight : Ext.emptyFn,
109033     setPosition : Ext.emptyFn,
109034     setPagePosition : Ext.emptyFn,
109035     markInvalid : Ext.emptyFn,
109036     clearInvalid : Ext.emptyFn
109037 });
109038
109039 /**
109040  * Color picker provides a simple color palette for choosing colors. The picker can be rendered to any container. The
109041  * available default to a standard 40-color palette; this can be customized with the {@link #colors} config.
109042  *
109043  * Typically you will need to implement a handler function to be notified when the user chooses a color from the picker;
109044  * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
109045  *
109046  *     @example
109047  *     Ext.create('Ext.picker.Color', {
109048  *         value: '993300',  // initial selected color
109049  *         renderTo: Ext.getBody(),
109050  *         listeners: {
109051  *             select: function(picker, selColor) {
109052  *                 alert(selColor);
109053  *             }
109054  *         }
109055  *     });
109056  */
109057 Ext.define('Ext.picker.Color', {
109058     extend: 'Ext.Component',
109059     requires: 'Ext.XTemplate',
109060     alias: 'widget.colorpicker',
109061     alternateClassName: 'Ext.ColorPalette',
109062
109063     /**
109064      * @cfg {String} [componentCls='x-color-picker']
109065      * The CSS class to apply to the containing element.
109066      */
109067     componentCls : Ext.baseCSSPrefix + 'color-picker',
109068
109069     /**
109070      * @cfg {String} [selectedCls='x-color-picker-selected']
109071      * The CSS class to apply to the selected element
109072      */
109073     selectedCls: Ext.baseCSSPrefix + 'color-picker-selected',
109074
109075     /**
109076      * @cfg {String} value
109077      * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that the hex
109078      * codes are case-sensitive.
109079      */
109080     value : null,
109081
109082     /**
109083      * @cfg {String} clickEvent
109084      * The DOM event that will cause a color to be selected. This can be any valid event name (dblclick, contextmenu).
109085      */
109086     clickEvent :'click',
109087
109088     /**
109089      * @cfg {Boolean} allowReselect
109090      * If set to true then reselecting a color that is already selected fires the {@link #select} event
109091      */
109092     allowReselect : false,
109093
109094     /**
109095      * @property {String[]} colors
109096      * An array of 6-digit color hex code strings (without the # symbol). This array can contain any number of colors,
109097      * and each hex code should be unique. The width of the picker is controlled via CSS by adjusting the width property
109098      * of the 'x-color-picker' class (or assigning a custom class), so you can balance the number of colors with the
109099      * width setting until the box is symmetrical.
109100      *
109101      * You can override individual colors if needed:
109102      *
109103      *     var cp = new Ext.picker.Color();
109104      *     cp.colors[0] = 'FF0000';  // change the first box to red
109105      *
109106      * Or you can provide a custom array of your own for complete control:
109107      *
109108      *     var cp = new Ext.picker.Color();
109109      *     cp.colors = ['000000', '993300', '333300'];
109110      */
109111     colors : [
109112         '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
109113         '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
109114         'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
109115         'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
109116         'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
109117     ],
109118
109119     /**
109120      * @cfg {Function} handler
109121      * A function that will handle the select event of this picker. The handler is passed the following parameters:
109122      *
109123      * - `picker` : ColorPicker
109124      *
109125      *   The {@link Ext.picker.Color picker}.
109126      *
109127      * - `color` : String
109128      *
109129      *   The 6-digit color hex code (without the # symbol).
109130      */
109131
109132     /**
109133      * @cfg {Object} scope
109134      * The scope (`this` reference) in which the `{@link #handler}` function will be called. Defaults to this
109135      * Color picker instance.
109136      */
109137
109138     colorRe: /(?:^|\s)color-(.{6})(?:\s|$)/,
109139     
109140     renderTpl: [
109141         '<tpl for="colors">',
109142             '<a href="#" class="color-{.}" hidefocus="on">',
109143                 '<em><span style="background:#{.}" unselectable="on">&#160;</span></em>',
109144             '</a>',
109145         '</tpl>'
109146     ],
109147
109148     // private
109149     initComponent : function(){
109150         var me = this;
109151
109152         me.callParent(arguments);
109153         me.addEvents(
109154             /**
109155              * @event select
109156              * Fires when a color is selected
109157              * @param {Ext.picker.Color} this
109158              * @param {String} color The 6-digit color hex code (without the # symbol)
109159              */
109160             'select'
109161         );
109162
109163         if (me.handler) {
109164             me.on('select', me.handler, me.scope, true);
109165         }
109166     },
109167
109168
109169     // private
109170     onRender : function(container, position){
109171         var me = this,
109172             clickEvent = me.clickEvent;
109173
109174         Ext.apply(me.renderData, {
109175             itemCls: me.itemCls,
109176             colors: me.colors
109177         });
109178         me.callParent(arguments);
109179
109180         me.mon(me.el, clickEvent, me.handleClick, me, {delegate: 'a'});
109181         // always stop following the anchors
109182         if(clickEvent != 'click'){
109183             me.mon(me.el, 'click', Ext.emptyFn, me, {delegate: 'a', stopEvent: true});
109184         }
109185     },
109186
109187     // private
109188     afterRender : function(){
109189         var me = this,
109190             value;
109191
109192         me.callParent(arguments);
109193         if (me.value) {
109194             value = me.value;
109195             me.value = null;
109196             me.select(value, true);
109197         }
109198     },
109199
109200     // private
109201     handleClick : function(event, target){
109202         var me = this,
109203             color;
109204
109205         event.stopEvent();
109206         if (!me.disabled) {
109207             color = target.className.match(me.colorRe)[1];
109208             me.select(color.toUpperCase());
109209         }
109210     },
109211
109212     /**
109213      * Selects the specified color in the picker (fires the {@link #select} event)
109214      * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
109215      * @param {Boolean} suppressEvent (optional) True to stop the select event from firing. Defaults to false.
109216      */
109217     select : function(color, suppressEvent){
109218
109219         var me = this,
109220             selectedCls = me.selectedCls,
109221             value = me.value,
109222             el;
109223
109224         color = color.replace('#', '');
109225         if (!me.rendered) {
109226             me.value = color;
109227             return;
109228         }
109229
109230
109231         if (color != value || me.allowReselect) {
109232             el = me.el;
109233
109234             if (me.value) {
109235                 el.down('a.color-' + value).removeCls(selectedCls);
109236             }
109237             el.down('a.color-' + color).addCls(selectedCls);
109238             me.value = color;
109239             if (suppressEvent !== true) {
109240                 me.fireEvent('select', me, color);
109241             }
109242         }
109243     },
109244
109245     /**
109246      * Get the currently selected color value.
109247      * @return {String} value The selected value. Null if nothing is selected.
109248      */
109249     getValue: function(){
109250         return this.value || null;
109251     }
109252 });
109253
109254 /**
109255  * @private
109256  * @class Ext.layout.component.field.HtmlEditor
109257  * @extends Ext.layout.component.field.Field
109258  * Layout class for {@link Ext.form.field.HtmlEditor} fields. Sizes the toolbar, textarea, and iframe elements.
109259  * @private
109260  */
109261
109262 Ext.define('Ext.layout.component.field.HtmlEditor', {
109263     extend: 'Ext.layout.component.field.Field',
109264     alias: ['layout.htmleditor'],
109265
109266     type: 'htmleditor',
109267
109268     sizeBodyContents: function(width, height) {
109269         var me = this,
109270             owner = me.owner,
109271             bodyEl = owner.bodyEl,
109272             toolbar = owner.getToolbar(),
109273             textarea = owner.textareaEl,
109274             iframe = owner.iframeEl,
109275             editorHeight;
109276
109277         if (Ext.isNumber(width)) {
109278             width -= bodyEl.getFrameWidth('lr');
109279         }
109280         toolbar.setWidth(width);
109281         textarea.setWidth(width);
109282         iframe.setWidth(width);
109283
109284         // If fixed height, subtract toolbar height from the input area height
109285         if (Ext.isNumber(height)) {
109286             editorHeight = height - toolbar.getHeight() - bodyEl.getFrameWidth('tb');
109287             textarea.setHeight(editorHeight);
109288             iframe.setHeight(editorHeight);
109289         }
109290     }
109291 });
109292 /**
109293  * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
109294  * automatically hidden when needed. These are noted in the config options where appropriate.
109295  *
109296  * The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
109297  * enabled by default unless the global {@link Ext.tip.QuickTipManager} singleton is
109298  * {@link Ext.tip.QuickTipManager#init initialized}.
109299  *
109300  * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an
109301  * Editor within any element that has display set to 'none' can cause problems in Safari and Firefox due to their
109302  * default iframe reloading bugs.
109303  *
109304  * # Example usage
109305  *
109306  * Simple example rendered with default options:
109307  *
109308  *     @example
109309  *     Ext.tip.QuickTipManager.init();  // enable tooltips
109310  *     Ext.create('Ext.form.HtmlEditor', {
109311  *         width: 580,
109312  *         height: 250,
109313  *         renderTo: Ext.getBody()
109314  *     });
109315  *
109316  * Passed via xtype into a container and with custom options:
109317  *
109318  *     @example
109319  *     Ext.tip.QuickTipManager.init();  // enable tooltips
109320  *     new Ext.panel.Panel({
109321  *         title: 'HTML Editor',
109322  *         renderTo: Ext.getBody(),
109323  *         width: 550,
109324  *         height: 250,
109325  *         frame: true,
109326  *         layout: 'fit',
109327  *         items: {
109328  *             xtype: 'htmleditor',
109329  *             enableColors: false,
109330  *             enableAlignments: false
109331  *         }
109332  *     });
109333  */
109334 Ext.define('Ext.form.field.HtmlEditor', {
109335     extend:'Ext.Component',
109336     mixins: {
109337         labelable: 'Ext.form.Labelable',
109338         field: 'Ext.form.field.Field'
109339     },
109340     alias: 'widget.htmleditor',
109341     alternateClassName: 'Ext.form.HtmlEditor',
109342     requires: [
109343         'Ext.tip.QuickTipManager',
109344         'Ext.picker.Color',
109345         'Ext.toolbar.Item',
109346         'Ext.toolbar.Toolbar',
109347         'Ext.util.Format',
109348         'Ext.layout.component.field.HtmlEditor'
109349     ],
109350
109351     fieldSubTpl: [
109352         '<div id="{cmpId}-toolbarWrap" class="{toolbarWrapCls}"></div>',
109353         '<textarea id="{cmpId}-textareaEl" name="{name}" tabIndex="-1" class="{textareaCls}" ',
109354             'style="{size}" autocomplete="off"></textarea>',
109355         '<iframe id="{cmpId}-iframeEl" name="{iframeName}" frameBorder="0" style="overflow:auto;{size}" src="{iframeSrc}"></iframe>',
109356         {
109357             compiled: true,
109358             disableFormats: true
109359         }
109360     ],
109361
109362     /**
109363      * @cfg {Boolean} enableFormat
109364      * Enable the bold, italic and underline buttons
109365      */
109366     enableFormat : true,
109367     /**
109368      * @cfg {Boolean} enableFontSize
109369      * Enable the increase/decrease font size buttons
109370      */
109371     enableFontSize : true,
109372     /**
109373      * @cfg {Boolean} enableColors
109374      * Enable the fore/highlight color buttons
109375      */
109376     enableColors : true,
109377     /**
109378      * @cfg {Boolean} enableAlignments
109379      * Enable the left, center, right alignment buttons
109380      */
109381     enableAlignments : true,
109382     /**
109383      * @cfg {Boolean} enableLists
109384      * Enable the bullet and numbered list buttons. Not available in Safari.
109385      */
109386     enableLists : true,
109387     /**
109388      * @cfg {Boolean} enableSourceEdit
109389      * Enable the switch to source edit button. Not available in Safari.
109390      */
109391     enableSourceEdit : true,
109392     /**
109393      * @cfg {Boolean} enableLinks
109394      * Enable the create link button. Not available in Safari.
109395      */
109396     enableLinks : true,
109397     /**
109398      * @cfg {Boolean} enableFont
109399      * Enable font selection. Not available in Safari.
109400      */
109401     enableFont : true,
109402     /**
109403      * @cfg {String} createLinkText
109404      * The default text for the create link prompt
109405      */
109406     createLinkText : 'Please enter the URL for the link:',
109407     /**
109408      * @cfg {String} [defaultLinkValue='http://']
109409      * The default value for the create link prompt
109410      */
109411     defaultLinkValue : 'http:/'+'/',
109412     /**
109413      * @cfg {String[]} fontFamilies
109414      * An array of available font families
109415      */
109416     fontFamilies : [
109417         'Arial',
109418         'Courier New',
109419         'Tahoma',
109420         'Times New Roman',
109421         'Verdana'
109422     ],
109423     defaultFont: 'tahoma',
109424     /**
109425      * @cfg {String} defaultValue
109426      * A default value to be put into the editor to resolve focus issues (defaults to (Non-breaking space) in Opera
109427      * and IE6, â€‹(Zero-width space) in all other browsers).
109428      */
109429     defaultValue: (Ext.isOpera || Ext.isIE6) ? '&#160;' : '&#8203;',
109430
109431     fieldBodyCls: Ext.baseCSSPrefix + 'html-editor-wrap',
109432
109433     componentLayout: 'htmleditor',
109434
109435     // private properties
109436     initialized : false,
109437     activated : false,
109438     sourceEditMode : false,
109439     iframePad:3,
109440     hideMode:'offsets',
109441
109442     maskOnDisable: true,
109443
109444     // private
109445     initComponent : function(){
109446         var me = this;
109447
109448         me.addEvents(
109449             /**
109450              * @event initialize
109451              * Fires when the editor is fully initialized (including the iframe)
109452              * @param {Ext.form.field.HtmlEditor} this
109453              */
109454             'initialize',
109455             /**
109456              * @event activate
109457              * Fires when the editor is first receives the focus. Any insertion must wait until after this event.
109458              * @param {Ext.form.field.HtmlEditor} this
109459              */
109460             'activate',
109461              /**
109462              * @event beforesync
109463              * Fires before the textarea is updated with content from the editor iframe. Return false to cancel the
109464              * sync.
109465              * @param {Ext.form.field.HtmlEditor} this
109466              * @param {String} html
109467              */
109468             'beforesync',
109469              /**
109470              * @event beforepush
109471              * Fires before the iframe editor is updated with content from the textarea. Return false to cancel the
109472              * push.
109473              * @param {Ext.form.field.HtmlEditor} this
109474              * @param {String} html
109475              */
109476             'beforepush',
109477              /**
109478              * @event sync
109479              * Fires when the textarea is updated with content from the editor iframe.
109480              * @param {Ext.form.field.HtmlEditor} this
109481              * @param {String} html
109482              */
109483             'sync',
109484              /**
109485              * @event push
109486              * Fires when the iframe editor is updated with content from the textarea.
109487              * @param {Ext.form.field.HtmlEditor} this
109488              * @param {String} html
109489              */
109490             'push',
109491              /**
109492              * @event editmodechange
109493              * Fires when the editor switches edit modes
109494              * @param {Ext.form.field.HtmlEditor} this
109495              * @param {Boolean} sourceEdit True if source edit, false if standard editing.
109496              */
109497             'editmodechange'
109498         );
109499
109500         me.callParent(arguments);
109501
109502         // Init mixins
109503         me.initLabelable();
109504         me.initField();
109505     },
109506
109507     /**
109508      * Called when the editor creates its toolbar. Override this method if you need to
109509      * add custom toolbar buttons.
109510      * @param {Ext.form.field.HtmlEditor} editor
109511      * @protected
109512      */
109513     createToolbar : function(editor){
109514         var me = this,
109515             items = [],
109516             tipsEnabled = Ext.tip.QuickTipManager && Ext.tip.QuickTipManager.isEnabled(),
109517             baseCSSPrefix = Ext.baseCSSPrefix,
109518             fontSelectItem, toolbar, undef;
109519
109520         function btn(id, toggle, handler){
109521             return {
109522                 itemId : id,
109523                 cls : baseCSSPrefix + 'btn-icon',
109524                 iconCls: baseCSSPrefix + 'edit-'+id,
109525                 enableToggle:toggle !== false,
109526                 scope: editor,
109527                 handler:handler||editor.relayBtnCmd,
109528                 clickEvent:'mousedown',
109529                 tooltip: tipsEnabled ? editor.buttonTips[id] || undef : undef,
109530                 overflowText: editor.buttonTips[id].title || undef,
109531                 tabIndex:-1
109532             };
109533         }
109534
109535
109536         if (me.enableFont && !Ext.isSafari2) {
109537             fontSelectItem = Ext.widget('component', {
109538                 renderTpl: [
109539                     '<select id="{id}-selectEl" class="{cls}">',
109540                         '<tpl for="fonts">',
109541                             '<option value="{[values.toLowerCase()]}" style="font-family:{.}"<tpl if="values.toLowerCase()==parent.defaultFont"> selected</tpl>>{.}</option>',
109542                         '</tpl>',
109543                     '</select>'
109544                 ],
109545                 renderData: {
109546                     cls: baseCSSPrefix + 'font-select',
109547                     fonts: me.fontFamilies,
109548                     defaultFont: me.defaultFont
109549                 },
109550                 childEls: ['selectEl'],
109551                 onDisable: function() {
109552                     var selectEl = this.selectEl;
109553                     if (selectEl) {
109554                         selectEl.dom.disabled = true;
109555                     }
109556                     Ext.Component.superclass.onDisable.apply(this, arguments);
109557                 },
109558                 onEnable: function() {
109559                     var selectEl = this.selectEl;
109560                     if (selectEl) {
109561                         selectEl.dom.disabled = false;
109562                     }
109563                     Ext.Component.superclass.onEnable.apply(this, arguments);
109564                 }
109565             });
109566
109567             items.push(
109568                 fontSelectItem,
109569                 '-'
109570             );
109571         }
109572
109573         if (me.enableFormat) {
109574             items.push(
109575                 btn('bold'),
109576                 btn('italic'),
109577                 btn('underline')
109578             );
109579         }
109580
109581         if (me.enableFontSize) {
109582             items.push(
109583                 '-',
109584                 btn('increasefontsize', false, me.adjustFont),
109585                 btn('decreasefontsize', false, me.adjustFont)
109586             );
109587         }
109588
109589         if (me.enableColors) {
109590             items.push(
109591                 '-', {
109592                     itemId: 'forecolor',
109593                     cls: baseCSSPrefix + 'btn-icon',
109594                     iconCls: baseCSSPrefix + 'edit-forecolor',
109595                     overflowText: editor.buttonTips.forecolor.title,
109596                     tooltip: tipsEnabled ? editor.buttonTips.forecolor || undef : undef,
109597                     tabIndex:-1,
109598                     menu : Ext.widget('menu', {
109599                         plain: true,
109600                         items: [{
109601                             xtype: 'colorpicker',
109602                             allowReselect: true,
109603                             focus: Ext.emptyFn,
109604                             value: '000000',
109605                             plain: true,
109606                             clickEvent: 'mousedown',
109607                             handler: function(cp, color) {
109608                                 me.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
109609                                 me.deferFocus();
109610                                 this.up('menu').hide();
109611                             }
109612                         }]
109613                     })
109614                 }, {
109615                     itemId: 'backcolor',
109616                     cls: baseCSSPrefix + 'btn-icon',
109617                     iconCls: baseCSSPrefix + 'edit-backcolor',
109618                     overflowText: editor.buttonTips.backcolor.title,
109619                     tooltip: tipsEnabled ? editor.buttonTips.backcolor || undef : undef,
109620                     tabIndex:-1,
109621                     menu : Ext.widget('menu', {
109622                         plain: true,
109623                         items: [{
109624                             xtype: 'colorpicker',
109625                             focus: Ext.emptyFn,
109626                             value: 'FFFFFF',
109627                             plain: true,
109628                             allowReselect: true,
109629                             clickEvent: 'mousedown',
109630                             handler: function(cp, color) {
109631                                 if (Ext.isGecko) {
109632                                     me.execCmd('useCSS', false);
109633                                     me.execCmd('hilitecolor', color);
109634                                     me.execCmd('useCSS', true);
109635                                     me.deferFocus();
109636                                 } else {
109637                                     me.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
109638                                     me.deferFocus();
109639                                 }
109640                                 this.up('menu').hide();
109641                             }
109642                         }]
109643                     })
109644                 }
109645             );
109646         }
109647
109648         if (me.enableAlignments) {
109649             items.push(
109650                 '-',
109651                 btn('justifyleft'),
109652                 btn('justifycenter'),
109653                 btn('justifyright')
109654             );
109655         }
109656
109657         if (!Ext.isSafari2) {
109658             if (me.enableLinks) {
109659                 items.push(
109660                     '-',
109661                     btn('createlink', false, me.createLink)
109662                 );
109663             }
109664
109665             if (me.enableLists) {
109666                 items.push(
109667                     '-',
109668                     btn('insertorderedlist'),
109669                     btn('insertunorderedlist')
109670                 );
109671             }
109672             if (me.enableSourceEdit) {
109673                 items.push(
109674                     '-',
109675                     btn('sourceedit', true, function(btn){
109676                         me.toggleSourceEdit(!me.sourceEditMode);
109677                     })
109678                 );
109679             }
109680         }
109681
109682         // build the toolbar
109683         toolbar = Ext.widget('toolbar', {
109684             renderTo: me.toolbarWrap,
109685             enableOverflow: true,
109686             items: items
109687         });
109688
109689         if (fontSelectItem) {
109690             me.fontSelect = fontSelectItem.selectEl;
109691
109692             me.mon(me.fontSelect, 'change', function(){
109693                 me.relayCmd('fontname', me.fontSelect.dom.value);
109694                 me.deferFocus();
109695             });
109696         }
109697
109698         // stop form submits
109699         me.mon(toolbar.el, 'click', function(e){
109700             e.preventDefault();
109701         });
109702
109703         me.toolbar = toolbar;
109704     },
109705
109706     onDisable: function() {
109707         this.bodyEl.mask();
109708         this.callParent(arguments);
109709     },
109710
109711     onEnable: function() {
109712         this.bodyEl.unmask();
109713         this.callParent(arguments);
109714     },
109715
109716     /**
109717      * Sets the read only state of this field.
109718      * @param {Boolean} readOnly Whether the field should be read only.
109719      */
109720     setReadOnly: function(readOnly) {
109721         var me = this,
109722             textareaEl = me.textareaEl,
109723             iframeEl = me.iframeEl,
109724             body;
109725
109726         me.readOnly = readOnly;
109727
109728         if (textareaEl) {
109729             textareaEl.dom.readOnly = readOnly;
109730         }
109731
109732         if (me.initialized) {
109733             body = me.getEditorBody();
109734             if (Ext.isIE) {
109735                 // Hide the iframe while setting contentEditable so it doesn't grab focus
109736                 iframeEl.setDisplayed(false);
109737                 body.contentEditable = !readOnly;
109738                 iframeEl.setDisplayed(true);
109739             } else {
109740                 me.setDesignMode(!readOnly);
109741             }
109742             if (body) {
109743                 body.style.cursor = readOnly ? 'default' : 'text';
109744             }
109745             me.disableItems(readOnly);
109746         }
109747     },
109748
109749     /**
109750      * Called when the editor initializes the iframe with HTML contents. Override this method if you
109751      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
109752      *
109753      * **Note:** IE8-Standards has unwanted scroller behavior, so the default meta tag forces IE7 compatibility.
109754      * Also note that forcing IE7 mode works when the page is loaded normally, but if you are using IE's Web
109755      * Developer Tools to manually set the document mode, that will take precedence and override what this
109756      * code sets by default. This can be confusing when developing, but is not a user-facing issue.
109757      * @protected
109758      */
109759     getDocMarkup: function() {
109760         var me = this,
109761             h = me.iframeEl.getHeight() - me.iframePad * 2;
109762         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);
109763     },
109764
109765     // private
109766     getEditorBody: function() {
109767         var doc = this.getDoc();
109768         return doc.body || doc.documentElement;
109769     },
109770
109771     // private
109772     getDoc: function() {
109773         return (!Ext.isIE && this.iframeEl.dom.contentDocument) || this.getWin().document;
109774     },
109775
109776     // private
109777     getWin: function() {
109778         return Ext.isIE ? this.iframeEl.dom.contentWindow : window.frames[this.iframeEl.dom.name];
109779     },
109780
109781     // private
109782     onRender: function() {
109783         var me = this;
109784
109785         me.onLabelableRender();
109786
109787         me.addChildEls('toolbarWrap', 'iframeEl', 'textareaEl');
109788
109789         me.callParent(arguments);
109790
109791         me.textareaEl.dom.value = me.value || '';
109792
109793         // Start polling for when the iframe document is ready to be manipulated
109794         me.monitorTask = Ext.TaskManager.start({
109795             run: me.checkDesignMode,
109796             scope: me,
109797             interval:100
109798         });
109799
109800         me.createToolbar(me);
109801         me.disableItems(true);
109802     },
109803
109804     initRenderTpl: function() {
109805         var me = this;
109806         if (!me.hasOwnProperty('renderTpl')) {
109807             me.renderTpl = me.getTpl('labelableRenderTpl');
109808         }
109809         return me.callParent();
109810     },
109811
109812     initRenderData: function() {
109813         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
109814     },
109815
109816     getSubTplData: function() {
109817         var cssPrefix = Ext.baseCSSPrefix;
109818         return {
109819             cmpId: this.id,
109820             id: this.getInputId(),
109821             toolbarWrapCls: cssPrefix + 'html-editor-tb',
109822             textareaCls: cssPrefix + 'hidden',
109823             iframeName: Ext.id(),
109824             iframeSrc: Ext.SSL_SECURE_URL,
109825             size: 'height:100px;'
109826         };
109827     },
109828
109829     getSubTplMarkup: function() {
109830         var data = this.getSubTplData();
109831         return this.getTpl('fieldSubTpl').apply(data);
109832     },
109833
109834     getBodyNaturalWidth: function() {
109835         return 565;
109836     },
109837
109838     initFrameDoc: function() {
109839         var me = this,
109840             doc, task;
109841
109842         Ext.TaskManager.stop(me.monitorTask);
109843
109844         doc = me.getDoc();
109845         me.win = me.getWin();
109846
109847         doc.open();
109848         doc.write(me.getDocMarkup());
109849         doc.close();
109850
109851         task = { // must defer to wait for browser to be ready
109852             run: function() {
109853                 var doc = me.getDoc();
109854                 if (doc.body || doc.readyState === 'complete') {
109855                     Ext.TaskManager.stop(task);
109856                     me.setDesignMode(true);
109857                     Ext.defer(me.initEditor, 10, me);
109858                 }
109859             },
109860             interval : 10,
109861             duration:10000,
109862             scope: me
109863         };
109864         Ext.TaskManager.start(task);
109865     },
109866
109867     checkDesignMode: function() {
109868         var me = this,
109869             doc = me.getDoc();
109870         if (doc && (!doc.editorInitialized || me.getDesignMode() !== 'on')) {
109871             me.initFrameDoc();
109872         }
109873     },
109874
109875     /**
109876      * @private
109877      * Sets current design mode. To enable, mode can be true or 'on', off otherwise
109878      */
109879     setDesignMode: function(mode) {
109880         var me = this,
109881             doc = me.getDoc();
109882         if (doc) {
109883             if (me.readOnly) {
109884                 mode = false;
109885             }
109886             doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off';
109887         }
109888     },
109889
109890     // private
109891     getDesignMode: function() {
109892         var doc = this.getDoc();
109893         return !doc ? '' : String(doc.designMode).toLowerCase();
109894     },
109895
109896     disableItems: function(disabled) {
109897         this.getToolbar().items.each(function(item){
109898             if(item.getItemId() !== 'sourceedit'){
109899                 item.setDisabled(disabled);
109900             }
109901         });
109902     },
109903
109904     /**
109905      * Toggles the editor between standard and source edit mode.
109906      * @param {Boolean} sourceEditMode (optional) True for source edit, false for standard
109907      */
109908     toggleSourceEdit: function(sourceEditMode) {
109909         var me = this,
109910             iframe = me.iframeEl,
109911             textarea = me.textareaEl,
109912             hiddenCls = Ext.baseCSSPrefix + 'hidden',
109913             btn = me.getToolbar().getComponent('sourceedit');
109914
109915         if (!Ext.isBoolean(sourceEditMode)) {
109916             sourceEditMode = !me.sourceEditMode;
109917         }
109918         me.sourceEditMode = sourceEditMode;
109919
109920         if (btn.pressed !== sourceEditMode) {
109921             btn.toggle(sourceEditMode);
109922         }
109923         if (sourceEditMode) {
109924             me.disableItems(true);
109925             me.syncValue();
109926             iframe.addCls(hiddenCls);
109927             textarea.removeCls(hiddenCls);
109928             textarea.dom.removeAttribute('tabIndex');
109929             textarea.focus();
109930         }
109931         else {
109932             if (me.initialized) {
109933                 me.disableItems(me.readOnly);
109934             }
109935             me.pushValue();
109936             iframe.removeCls(hiddenCls);
109937             textarea.addCls(hiddenCls);
109938             textarea.dom.setAttribute('tabIndex', -1);
109939             me.deferFocus();
109940         }
109941         me.fireEvent('editmodechange', me, sourceEditMode);
109942         me.doComponentLayout();
109943     },
109944
109945     // private used internally
109946     createLink : function() {
109947         var url = prompt(this.createLinkText, this.defaultLinkValue);
109948         if (url && url !== 'http:/'+'/') {
109949             this.relayCmd('createlink', url);
109950         }
109951     },
109952
109953     clearInvalid: Ext.emptyFn,
109954
109955     // docs inherit from Field
109956     setValue: function(value) {
109957         var me = this,
109958             textarea = me.textareaEl;
109959         me.mixins.field.setValue.call(me, value);
109960         if (value === null || value === undefined) {
109961             value = '';
109962         }
109963         if (textarea) {
109964             textarea.dom.value = value;
109965         }
109966         me.pushValue();
109967         return this;
109968     },
109969
109970     /**
109971      * If you need/want custom HTML cleanup, this is the method you should override.
109972      * @param {String} html The HTML to be cleaned
109973      * @return {String} The cleaned HTML
109974      * @protected
109975      */
109976     cleanHtml: function(html) {
109977         html = String(html);
109978         if (Ext.isWebKit) { // strip safari nonsense
109979             html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
109980         }
109981
109982         /*
109983          * Neat little hack. Strips out all the non-digit characters from the default
109984          * value and compares it to the character code of the first character in the string
109985          * because it can cause encoding issues when posted to the server.
109986          */
109987         if (html.charCodeAt(0) === this.defaultValue.replace(/\D/g, '')) {
109988             html = html.substring(1);
109989         }
109990         return html;
109991     },
109992
109993     /**
109994      * Syncs the contents of the editor iframe with the textarea.
109995      * @protected
109996      */
109997     syncValue : function(){
109998         var me = this,
109999             body, html, bodyStyle, match;
110000         if (me.initialized) {
110001             body = me.getEditorBody();
110002             html = body.innerHTML;
110003             if (Ext.isWebKit) {
110004                 bodyStyle = body.getAttribute('style'); // Safari puts text-align styles on the body element!
110005                 match = bodyStyle.match(/text-align:(.*?);/i);
110006                 if (match && match[1]) {
110007                     html = '<div style="' + match[0] + '">' + html + '</div>';
110008                 }
110009             }
110010             html = me.cleanHtml(html);
110011             if (me.fireEvent('beforesync', me, html) !== false) {
110012                 me.textareaEl.dom.value = html;
110013                 me.fireEvent('sync', me, html);
110014             }
110015         }
110016     },
110017
110018     //docs inherit from Field
110019     getValue : function() {
110020         var me = this,
110021             value;
110022         if (!me.sourceEditMode) {
110023             me.syncValue();
110024         }
110025         value = me.rendered ? me.textareaEl.dom.value : me.value;
110026         me.value = value;
110027         return value;
110028     },
110029
110030     /**
110031      * Pushes the value of the textarea into the iframe editor.
110032      * @protected
110033      */
110034     pushValue: function() {
110035         var me = this,
110036             v;
110037         if(me.initialized){
110038             v = me.textareaEl.dom.value || '';
110039             if (!me.activated && v.length < 1) {
110040                 v = me.defaultValue;
110041             }
110042             if (me.fireEvent('beforepush', me, v) !== false) {
110043                 me.getEditorBody().innerHTML = v;
110044                 if (Ext.isGecko) {
110045                     // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
110046                     me.setDesignMode(false);  //toggle off first
110047                     me.setDesignMode(true);
110048                 }
110049                 me.fireEvent('push', me, v);
110050             }
110051         }
110052     },
110053
110054     // private
110055     deferFocus : function(){
110056          this.focus(false, true);
110057     },
110058
110059     getFocusEl: function() {
110060         var me = this,
110061             win = me.win;
110062         return win && !me.sourceEditMode ? win : me.textareaEl;
110063     },
110064
110065     // private
110066     initEditor : function(){
110067         //Destroying the component during/before initEditor can cause issues.
110068         try {
110069             var me = this,
110070                 dbody = me.getEditorBody(),
110071                 ss = me.textareaEl.getStyles('font-size', 'font-family', 'background-image', 'background-repeat', 'background-color', 'color'),
110072                 doc,
110073                 fn;
110074
110075             ss['background-attachment'] = 'fixed'; // w3c
110076             dbody.bgProperties = 'fixed'; // ie
110077
110078             Ext.DomHelper.applyStyles(dbody, ss);
110079
110080             doc = me.getDoc();
110081
110082             if (doc) {
110083                 try {
110084                     Ext.EventManager.removeAll(doc);
110085                 } catch(e) {}
110086             }
110087
110088             /*
110089              * We need to use createDelegate here, because when using buffer, the delayed task is added
110090              * as a property to the function. When the listener is removed, the task is deleted from the function.
110091              * Since onEditorEvent is shared on the prototype, if we have multiple html editors, the first time one of the editors
110092              * is destroyed, it causes the fn to be deleted from the prototype, which causes errors. Essentially, we're just anonymizing the function.
110093              */
110094             fn = Ext.Function.bind(me.onEditorEvent, me);
110095             Ext.EventManager.on(doc, {
110096                 mousedown: fn,
110097                 dblclick: fn,
110098                 click: fn,
110099                 keyup: fn,
110100                 buffer:100
110101             });
110102
110103             // These events need to be relayed from the inner document (where they stop
110104             // bubbling) up to the outer document. This has to be done at the DOM level so
110105             // the event reaches listeners on elements like the document body. The effected
110106             // mechanisms that depend on this bubbling behavior are listed to the right
110107             // of the event.
110108             fn = me.onRelayedEvent;
110109             Ext.EventManager.on(doc, {
110110                 mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
110111                 mousemove: fn, // window resize drag detection
110112                 mouseup: fn,   // window resize termination
110113                 click: fn,     // not sure, but just to be safe
110114                 dblclick: fn,  // not sure again
110115                 scope: me
110116             });
110117
110118             if (Ext.isGecko) {
110119                 Ext.EventManager.on(doc, 'keypress', me.applyCommand, me);
110120             }
110121             if (me.fixKeys) {
110122                 Ext.EventManager.on(doc, 'keydown', me.fixKeys, me);
110123             }
110124
110125             // We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
110126             Ext.EventManager.on(window, 'unload', me.beforeDestroy, me);
110127             doc.editorInitialized = true;
110128
110129             me.initialized = true;
110130             me.pushValue();
110131             me.setReadOnly(me.readOnly);
110132             me.fireEvent('initialize', me);
110133         } catch(ex) {
110134             // ignore (why?)
110135         }
110136     },
110137
110138     // private
110139     beforeDestroy : function(){
110140         var me = this,
110141             monitorTask = me.monitorTask,
110142             doc, prop;
110143
110144         if (monitorTask) {
110145             Ext.TaskManager.stop(monitorTask);
110146         }
110147         if (me.rendered) {
110148             try {
110149                 doc = me.getDoc();
110150                 if (doc) {
110151                     Ext.EventManager.removeAll(doc);
110152                     for (prop in doc) {
110153                         if (doc.hasOwnProperty(prop)) {
110154                             delete doc[prop];
110155                         }
110156                     }
110157                 }
110158             } catch(e) {
110159                 // ignore (why?)
110160             }
110161             Ext.destroyMembers(me, 'tb', 'toolbarWrap', 'iframeEl', 'textareaEl');
110162         }
110163         me.callParent();
110164     },
110165
110166     // private
110167     onRelayedEvent: function (event) {
110168         // relay event from the iframe's document to the document that owns the iframe...
110169
110170         var iframeEl = this.iframeEl,
110171             iframeXY = iframeEl.getXY(),
110172             eventXY = event.getXY();
110173
110174         // the event from the inner document has XY relative to that document's origin,
110175         // so adjust it to use the origin of the iframe in the outer document:
110176         event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]];
110177
110178         event.injectEvent(iframeEl); // blame the iframe for the event...
110179
110180         event.xy = eventXY; // restore the original XY (just for safety)
110181     },
110182
110183     // private
110184     onFirstFocus : function(){
110185         var me = this,
110186             selection, range;
110187         me.activated = true;
110188         me.disableItems(me.readOnly);
110189         if (Ext.isGecko) { // prevent silly gecko errors
110190             me.win.focus();
110191             selection = me.win.getSelection();
110192             if (!selection.focusNode || selection.focusNode.nodeType !== 3) {
110193                 range = selection.getRangeAt(0);
110194                 range.selectNodeContents(me.getEditorBody());
110195                 range.collapse(true);
110196                 me.deferFocus();
110197             }
110198             try {
110199                 me.execCmd('useCSS', true);
110200                 me.execCmd('styleWithCSS', false);
110201             } catch(e) {
110202                 // ignore (why?)
110203             }
110204         }
110205         me.fireEvent('activate', me);
110206     },
110207
110208     // private
110209     adjustFont: function(btn) {
110210         var adjust = btn.getItemId() === 'increasefontsize' ? 1 : -1,
110211             size = this.getDoc().queryCommandValue('FontSize') || '2',
110212             isPxSize = Ext.isString(size) && size.indexOf('px') !== -1,
110213             isSafari;
110214         size = parseInt(size, 10);
110215         if (isPxSize) {
110216             // Safari 3 values
110217             // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
110218             if (size <= 10) {
110219                 size = 1 + adjust;
110220             }
110221             else if (size <= 13) {
110222                 size = 2 + adjust;
110223             }
110224             else if (size <= 16) {
110225                 size = 3 + adjust;
110226             }
110227             else if (size <= 18) {
110228                 size = 4 + adjust;
110229             }
110230             else if (size <= 24) {
110231                 size = 5 + adjust;
110232             }
110233             else {
110234                 size = 6 + adjust;
110235             }
110236             size = Ext.Number.constrain(size, 1, 6);
110237         } else {
110238             isSafari = Ext.isSafari;
110239             if (isSafari) { // safari
110240                 adjust *= 2;
110241             }
110242             size = Math.max(1, size + adjust) + (isSafari ? 'px' : 0);
110243         }
110244         this.execCmd('FontSize', size);
110245     },
110246
110247     // private
110248     onEditorEvent: function(e) {
110249         this.updateToolbar();
110250     },
110251
110252     /**
110253      * Triggers a toolbar update by reading the markup state of the current selection in the editor.
110254      * @protected
110255      */
110256     updateToolbar: function() {
110257         var me = this,
110258             btns, doc, name, fontSelect;
110259
110260         if (me.readOnly) {
110261             return;
110262         }
110263
110264         if (!me.activated) {
110265             me.onFirstFocus();
110266             return;
110267         }
110268
110269         btns = me.getToolbar().items.map;
110270         doc = me.getDoc();
110271
110272         if (me.enableFont && !Ext.isSafari2) {
110273             name = (doc.queryCommandValue('FontName') || me.defaultFont).toLowerCase();
110274             fontSelect = me.fontSelect.dom;
110275             if (name !== fontSelect.value) {
110276                 fontSelect.value = name;
110277             }
110278         }
110279
110280         function updateButtons() {
110281             Ext.Array.forEach(Ext.Array.toArray(arguments), function(name) {
110282                 btns[name].toggle(doc.queryCommandState(name));
110283             });
110284         }
110285         if(me.enableFormat){
110286             updateButtons('bold', 'italic', 'underline');
110287         }
110288         if(me.enableAlignments){
110289             updateButtons('justifyleft', 'justifycenter', 'justifyright');
110290         }
110291         if(!Ext.isSafari2 && me.enableLists){
110292             updateButtons('insertorderedlist', 'insertunorderedlist');
110293         }
110294
110295         Ext.menu.Manager.hideAll();
110296
110297         me.syncValue();
110298     },
110299
110300     // private
110301     relayBtnCmd: function(btn) {
110302         this.relayCmd(btn.getItemId());
110303     },
110304
110305     /**
110306      * Executes a Midas editor command on the editor document and performs necessary focus and toolbar updates.
110307      * **This should only be called after the editor is initialized.**
110308      * @param {String} cmd The Midas command
110309      * @param {String/Boolean} [value=null] The value to pass to the command
110310      */
110311     relayCmd: function(cmd, value) {
110312         Ext.defer(function() {
110313             var me = this;
110314             me.focus();
110315             me.execCmd(cmd, value);
110316             me.updateToolbar();
110317         }, 10, this);
110318     },
110319
110320     /**
110321      * Executes a Midas editor command directly on the editor document. For visual commands, you should use
110322      * {@link #relayCmd} instead. **This should only be called after the editor is initialized.**
110323      * @param {String} cmd The Midas command
110324      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
110325      */
110326     execCmd : function(cmd, value){
110327         var me = this,
110328             doc = me.getDoc(),
110329             undef;
110330         doc.execCommand(cmd, false, value === undef ? null : value);
110331         me.syncValue();
110332     },
110333
110334     // private
110335     applyCommand : function(e){
110336         if (e.ctrlKey) {
110337             var me = this,
110338                 c = e.getCharCode(), cmd;
110339             if (c > 0) {
110340                 c = String.fromCharCode(c);
110341                 switch (c) {
110342                     case 'b':
110343                         cmd = 'bold';
110344                     break;
110345                     case 'i':
110346                         cmd = 'italic';
110347                     break;
110348                     case 'u':
110349                         cmd = 'underline';
110350                     break;
110351                 }
110352                 if (cmd) {
110353                     me.win.focus();
110354                     me.execCmd(cmd);
110355                     me.deferFocus();
110356                     e.preventDefault();
110357                 }
110358             }
110359         }
110360     },
110361
110362     /**
110363      * Inserts the passed text at the current cursor position.
110364      * Note: the editor must be initialized and activated to insert text.
110365      * @param {String} text
110366      */
110367     insertAtCursor : function(text){
110368         var me = this,
110369             range;
110370
110371         if (me.activated) {
110372             me.win.focus();
110373             if (Ext.isIE) {
110374                 range = me.getDoc().selection.createRange();
110375                 if (range) {
110376                     range.pasteHTML(text);
110377                     me.syncValue();
110378                     me.deferFocus();
110379                 }
110380             }else{
110381                 me.execCmd('InsertHTML', text);
110382                 me.deferFocus();
110383             }
110384         }
110385     },
110386
110387     // private
110388     fixKeys: function() { // load time branching for fastest keydown performance
110389         if (Ext.isIE) {
110390             return function(e){
110391                 var me = this,
110392                     k = e.getKey(),
110393                     doc = me.getDoc(),
110394                     range, target;
110395                 if (k === e.TAB) {
110396                     e.stopEvent();
110397                     range = doc.selection.createRange();
110398                     if(range){
110399                         range.collapse(true);
110400                         range.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
110401                         me.deferFocus();
110402                     }
110403                 }
110404                 else if (k === e.ENTER) {
110405                     range = doc.selection.createRange();
110406                     if (range) {
110407                         target = range.parentElement();
110408                         if(!target || target.tagName.toLowerCase() !== 'li'){
110409                             e.stopEvent();
110410                             range.pasteHTML('<br />');
110411                             range.collapse(false);
110412                             range.select();
110413                         }
110414                     }
110415                 }
110416             };
110417         }
110418
110419         if (Ext.isOpera) {
110420             return function(e){
110421                 var me = this;
110422                 if (e.getKey() === e.TAB) {
110423                     e.stopEvent();
110424                     me.win.focus();
110425                     me.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');
110426                     me.deferFocus();
110427                 }
110428             };
110429         }
110430
110431         if (Ext.isWebKit) {
110432             return function(e){
110433                 var me = this,
110434                     k = e.getKey();
110435                 if (k === e.TAB) {
110436                     e.stopEvent();
110437                     me.execCmd('InsertText','\t');
110438                     me.deferFocus();
110439                 }
110440                 else if (k === e.ENTER) {
110441                     e.stopEvent();
110442                     me.execCmd('InsertHtml','<br /><br />');
110443                     me.deferFocus();
110444                 }
110445             };
110446         }
110447
110448         return null; // not needed, so null
110449     }(),
110450
110451     /**
110452      * Returns the editor's toolbar. **This is only available after the editor has been rendered.**
110453      * @return {Ext.toolbar.Toolbar}
110454      */
110455     getToolbar : function(){
110456         return this.toolbar;
110457     },
110458
110459     /**
110460      * @property {Object} buttonTips
110461      * Object collection of toolbar tooltips for the buttons in the editor. The key is the command id associated with
110462      * that button and the value is a valid QuickTips object. For example:
110463      *
110464      *     {
110465      *         bold : {
110466      *             title: 'Bold (Ctrl+B)',
110467      *             text: 'Make the selected text bold.',
110468      *             cls: 'x-html-editor-tip'
110469      *         },
110470      *         italic : {
110471      *             title: 'Italic (Ctrl+I)',
110472      *             text: 'Make the selected text italic.',
110473      *             cls: 'x-html-editor-tip'
110474      *         },
110475      *         ...
110476      */
110477     buttonTips : {
110478         bold : {
110479             title: 'Bold (Ctrl+B)',
110480             text: 'Make the selected text bold.',
110481             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110482         },
110483         italic : {
110484             title: 'Italic (Ctrl+I)',
110485             text: 'Make the selected text italic.',
110486             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110487         },
110488         underline : {
110489             title: 'Underline (Ctrl+U)',
110490             text: 'Underline the selected text.',
110491             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110492         },
110493         increasefontsize : {
110494             title: 'Grow Text',
110495             text: 'Increase the font size.',
110496             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110497         },
110498         decreasefontsize : {
110499             title: 'Shrink Text',
110500             text: 'Decrease the font size.',
110501             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110502         },
110503         backcolor : {
110504             title: 'Text Highlight Color',
110505             text: 'Change the background color of the selected text.',
110506             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110507         },
110508         forecolor : {
110509             title: 'Font Color',
110510             text: 'Change the color of the selected text.',
110511             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110512         },
110513         justifyleft : {
110514             title: 'Align Text Left',
110515             text: 'Align text to the left.',
110516             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110517         },
110518         justifycenter : {
110519             title: 'Center Text',
110520             text: 'Center text in the editor.',
110521             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110522         },
110523         justifyright : {
110524             title: 'Align Text Right',
110525             text: 'Align text to the right.',
110526             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110527         },
110528         insertunorderedlist : {
110529             title: 'Bullet List',
110530             text: 'Start a bulleted list.',
110531             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110532         },
110533         insertorderedlist : {
110534             title: 'Numbered List',
110535             text: 'Start a numbered list.',
110536             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110537         },
110538         createlink : {
110539             title: 'Hyperlink',
110540             text: 'Make the selected text a hyperlink.',
110541             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110542         },
110543         sourceedit : {
110544             title: 'Source Edit',
110545             text: 'Switch to source editing mode.',
110546             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110547         }
110548     }
110549
110550     // hide stuff that is not compatible
110551     /**
110552      * @event blur
110553      * @hide
110554      */
110555     /**
110556      * @event change
110557      * @hide
110558      */
110559     /**
110560      * @event focus
110561      * @hide
110562      */
110563     /**
110564      * @event specialkey
110565      * @hide
110566      */
110567     /**
110568      * @cfg {String} fieldCls @hide
110569      */
110570     /**
110571      * @cfg {String} focusCls @hide
110572      */
110573     /**
110574      * @cfg {String} autoCreate @hide
110575      */
110576     /**
110577      * @cfg {String} inputType @hide
110578      */
110579     /**
110580      * @cfg {String} invalidCls @hide
110581      */
110582     /**
110583      * @cfg {String} invalidText @hide
110584      */
110585     /**
110586      * @cfg {String} msgFx @hide
110587      */
110588     /**
110589      * @cfg {Boolean} allowDomMove @hide
110590      */
110591     /**
110592      * @cfg {String} applyTo @hide
110593      */
110594     /**
110595      * @cfg {String} readOnly  @hide
110596      */
110597     /**
110598      * @cfg {String} tabIndex  @hide
110599      */
110600     /**
110601      * @method validate
110602      * @hide
110603      */
110604 });
110605
110606 /**
110607  * @docauthor Robert Dougan <rob@sencha.com>
110608  *
110609  * Single radio field. Similar to checkbox, but automatically handles making sure only one radio is checked
110610  * at a time within a group of radios with the same name.
110611  *
110612  * # Labeling
110613  *
110614  * In addition to the {@link Ext.form.Labelable standard field labeling options}, radio buttons
110615  * may be given an optional {@link #boxLabel} which will be displayed immediately to the right of the input. Also
110616  * see {@link Ext.form.RadioGroup} for a convenient method of grouping related radio buttons.
110617  *
110618  * # Values
110619  *
110620  * The main value of a Radio field is a boolean, indicating whether or not the radio is checked.
110621  *
110622  * The following values will check the radio:
110623  *
110624  * - `true`
110625  * - `'true'`
110626  * - `'1'`
110627  * - `'on'`
110628  *
110629  * Any other value will uncheck it.
110630  *
110631  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be sent
110632  * as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set this
110633  * value if you have multiple radio buttons with the same {@link #name}, as is almost always the case.
110634  *
110635  * # Example usage
110636  *
110637  *     @example
110638  *     Ext.create('Ext.form.Panel', {
110639  *         title      : 'Order Form',
110640  *         width      : 300,
110641  *         bodyPadding: 10,
110642  *         renderTo   : Ext.getBody(),
110643  *         items: [
110644  *             {
110645  *                 xtype      : 'fieldcontainer',
110646  *                 fieldLabel : 'Size',
110647  *                 defaultType: 'radiofield',
110648  *                 defaults: {
110649  *                     flex: 1
110650  *                 },
110651  *                 layout: 'hbox',
110652  *                 items: [
110653  *                     {
110654  *                         boxLabel  : 'M',
110655  *                         name      : 'size',
110656  *                         inputValue: 'm',
110657  *                         id        : 'radio1'
110658  *                     }, {
110659  *                         boxLabel  : 'L',
110660  *                         name      : 'size',
110661  *                         inputValue: 'l',
110662  *                         id        : 'radio2'
110663  *                     }, {
110664  *                         boxLabel  : 'XL',
110665  *                         name      : 'size',
110666  *                         inputValue: 'xl',
110667  *                         id        : 'radio3'
110668  *                     }
110669  *                 ]
110670  *             },
110671  *             {
110672  *                 xtype      : 'fieldcontainer',
110673  *                 fieldLabel : 'Color',
110674  *                 defaultType: 'radiofield',
110675  *                 defaults: {
110676  *                     flex: 1
110677  *                 },
110678  *                 layout: 'hbox',
110679  *                 items: [
110680  *                     {
110681  *                         boxLabel  : 'Blue',
110682  *                         name      : 'color',
110683  *                         inputValue: 'blue',
110684  *                         id        : 'radio4'
110685  *                     }, {
110686  *                         boxLabel  : 'Grey',
110687  *                         name      : 'color',
110688  *                         inputValue: 'grey',
110689  *                         id        : 'radio5'
110690  *                     }, {
110691  *                         boxLabel  : 'Black',
110692  *                         name      : 'color',
110693  *                         inputValue: 'black',
110694  *                         id        : 'radio6'
110695  *                     }
110696  *                 ]
110697  *             }
110698  *         ],
110699  *         bbar: [
110700  *             {
110701  *                 text: 'Smaller Size',
110702  *                 handler: function() {
110703  *                     var radio1 = Ext.getCmp('radio1'),
110704  *                         radio2 = Ext.getCmp('radio2'),
110705  *                         radio3 = Ext.getCmp('radio3');
110706  *
110707  *                     //if L is selected, change to M
110708  *                     if (radio2.getValue()) {
110709  *                         radio1.setValue(true);
110710  *                         return;
110711  *                     }
110712  *
110713  *                     //if XL is selected, change to L
110714  *                     if (radio3.getValue()) {
110715  *                         radio2.setValue(true);
110716  *                         return;
110717  *                     }
110718  *
110719  *                     //if nothing is set, set size to S
110720  *                     radio1.setValue(true);
110721  *                 }
110722  *             },
110723  *             {
110724  *                 text: 'Larger Size',
110725  *                 handler: function() {
110726  *                     var radio1 = Ext.getCmp('radio1'),
110727  *                         radio2 = Ext.getCmp('radio2'),
110728  *                         radio3 = Ext.getCmp('radio3');
110729  *
110730  *                     //if M is selected, change to L
110731  *                     if (radio1.getValue()) {
110732  *                         radio2.setValue(true);
110733  *                         return;
110734  *                     }
110735  *
110736  *                     //if L is selected, change to XL
110737  *                     if (radio2.getValue()) {
110738  *                         radio3.setValue(true);
110739  *                         return;
110740  *                     }
110741  *
110742  *                     //if nothing is set, set size to XL
110743  *                     radio3.setValue(true);
110744  *                 }
110745  *             },
110746  *             '-',
110747  *             {
110748  *                 text: 'Select color',
110749  *                 menu: {
110750  *                     indent: false,
110751  *                     items: [
110752  *                         {
110753  *                             text: 'Blue',
110754  *                             handler: function() {
110755  *                                 var radio = Ext.getCmp('radio4');
110756  *                                 radio.setValue(true);
110757  *                             }
110758  *                         },
110759  *                         {
110760  *                             text: 'Grey',
110761  *                             handler: function() {
110762  *                                 var radio = Ext.getCmp('radio5');
110763  *                                 radio.setValue(true);
110764  *                             }
110765  *                         },
110766  *                         {
110767  *                             text: 'Black',
110768  *                             handler: function() {
110769  *                                 var radio = Ext.getCmp('radio6');
110770  *                                 radio.setValue(true);
110771  *                             }
110772  *                         }
110773  *                     ]
110774  *                 }
110775  *             }
110776  *         ]
110777  *     });
110778  */
110779 Ext.define('Ext.form.field.Radio', {
110780     extend:'Ext.form.field.Checkbox',
110781     alias: ['widget.radiofield', 'widget.radio'],
110782     alternateClassName: 'Ext.form.Radio',
110783     requires: ['Ext.form.RadioManager'],
110784
110785     isRadio: true,
110786
110787     /**
110788      * @cfg {String} uncheckedValue @hide
110789      */
110790
110791     // private
110792     inputType: 'radio',
110793     ariaRole: 'radio',
110794
110795     /**
110796      * If this radio is part of a group, it will return the selected value
110797      * @return {String}
110798      */
110799     getGroupValue: function() {
110800         var selected = this.getManager().getChecked(this.name);
110801         return selected ? selected.inputValue : null;
110802     },
110803
110804     /**
110805      * @private Handle click on the radio button
110806      */
110807     onBoxClick: function(e) {
110808         var me = this;
110809         if (!me.disabled && !me.readOnly) {
110810             this.setValue(true);
110811         }
110812     },
110813
110814     /**
110815      * Sets either the checked/unchecked status of this Radio, or, if a string value is passed, checks a sibling Radio
110816      * of the same name whose value is the value specified.
110817      * @param {String/Boolean} value Checked value, or the value of the sibling radio button to check.
110818      * @return {Ext.form.field.Radio} this
110819      */
110820     setValue: function(v) {
110821         var me = this,
110822             active;
110823
110824         if (Ext.isBoolean(v)) {
110825             me.callParent(arguments);
110826         } else {
110827             active = me.getManager().getWithValue(me.name, v).getAt(0);
110828             if (active) {
110829                 active.setValue(true);
110830             }
110831         }
110832         return me;
110833     },
110834
110835     /**
110836      * Returns the submit value for the checkbox which can be used when submitting forms.
110837      * @return {Boolean/Object} True if checked, null if not.
110838      */
110839     getSubmitValue: function() {
110840         return this.checked ? this.inputValue : null;
110841     },
110842
110843     getModelData: function() {
110844         return this.getSubmitData();
110845     },
110846
110847     // inherit docs
110848     onChange: function(newVal, oldVal) {
110849         var me = this;
110850         me.callParent(arguments);
110851
110852         if (newVal) {
110853             this.getManager().getByName(me.name).each(function(item){
110854                 if (item !== me) {
110855                     item.setValue(false);
110856                 }
110857             }, me);
110858         }
110859     },
110860
110861     // inherit docs
110862     getManager: function() {
110863         return Ext.form.RadioManager;
110864     }
110865 });
110866
110867 /**
110868  * A time picker which provides a list of times from which to choose. This is used by the Ext.form.field.Time
110869  * class to allow browsing and selection of valid times, but could also be used with other components.
110870  *
110871  * By default, all times starting at midnight and incrementing every 15 minutes will be presented. This list of
110872  * available times can be controlled using the {@link #minValue}, {@link #maxValue}, and {@link #increment}
110873  * configuration properties. The format of the times presented in the list can be customized with the {@link #format}
110874  * config.
110875  *
110876  * To handle when the user selects a time from the list, you can subscribe to the {@link #selectionchange} event.
110877  *
110878  *     @example
110879  *     Ext.create('Ext.picker.Time', {
110880  *        width: 60,
110881  *        minValue: Ext.Date.parse('04:30:00 AM', 'h:i:s A'),
110882  *        maxValue: Ext.Date.parse('08:00:00 AM', 'h:i:s A'),
110883  *        renderTo: Ext.getBody()
110884  *     });
110885  */
110886 Ext.define('Ext.picker.Time', {
110887     extend: 'Ext.view.BoundList',
110888     alias: 'widget.timepicker',
110889     requires: ['Ext.data.Store', 'Ext.Date'],
110890
110891     /**
110892      * @cfg {Date} minValue
110893      * The minimum time to be shown in the list of times. This must be a Date object (only the time fields will be
110894      * used); no parsing of String values will be done.
110895      */
110896
110897     /**
110898      * @cfg {Date} maxValue
110899      * The maximum time to be shown in the list of times. This must be a Date object (only the time fields will be
110900      * used); no parsing of String values will be done.
110901      */
110902
110903     /**
110904      * @cfg {Number} increment
110905      * The number of minutes between each time value in the list.
110906      */
110907     increment: 15,
110908
110909     /**
110910      * @cfg {String} format
110911      * The default time format string which can be overriden for localization support. The format must be valid
110912      * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i'
110913      * instead.
110914      */
110915     format : "g:i A",
110916
110917     /**
110918      * @hide
110919      * The field in the implicitly-generated Model objects that gets displayed in the list. This is
110920      * an internal field name only and is not useful to change via config.
110921      */
110922     displayField: 'disp',
110923
110924     /**
110925      * @private
110926      * Year, month, and day that all times will be normalized into internally.
110927      */
110928     initDate: [2008,0,1],
110929
110930     componentCls: Ext.baseCSSPrefix + 'timepicker',
110931
110932     /**
110933      * @hide
110934      */
110935     loadMask: false,
110936
110937     initComponent: function() {
110938         var me = this,
110939             dateUtil = Ext.Date,
110940             clearTime = dateUtil.clearTime,
110941             initDate = me.initDate;
110942
110943         // Set up absolute min and max for the entire day
110944         me.absMin = clearTime(new Date(initDate[0], initDate[1], initDate[2]));
110945         me.absMax = dateUtil.add(clearTime(new Date(initDate[0], initDate[1], initDate[2])), 'mi', (24 * 60) - 1);
110946
110947         me.store = me.createStore();
110948         me.updateList();
110949
110950         me.callParent();
110951     },
110952
110953     /**
110954      * Set the {@link #minValue} and update the list of available times. This must be a Date object (only the time
110955      * fields will be used); no parsing of String values will be done.
110956      * @param {Date} value
110957      */
110958     setMinValue: function(value) {
110959         this.minValue = value;
110960         this.updateList();
110961     },
110962
110963     /**
110964      * Set the {@link #maxValue} and update the list of available times. This must be a Date object (only the time
110965      * fields will be used); no parsing of String values will be done.
110966      * @param {Date} value
110967      */
110968     setMaxValue: function(value) {
110969         this.maxValue = value;
110970         this.updateList();
110971     },
110972
110973     /**
110974      * @private
110975      * Sets the year/month/day of the given Date object to the {@link #initDate}, so that only
110976      * the time fields are significant. This makes values suitable for time comparison.
110977      * @param {Date} date
110978      */
110979     normalizeDate: function(date) {
110980         var initDate = this.initDate;
110981         date.setFullYear(initDate[0], initDate[1], initDate[2]);
110982         return date;
110983     },
110984
110985     /**
110986      * Update the list of available times in the list to be constrained within the {@link #minValue}
110987      * and {@link #maxValue}.
110988      */
110989     updateList: function() {
110990         var me = this,
110991             min = me.normalizeDate(me.minValue || me.absMin),
110992             max = me.normalizeDate(me.maxValue || me.absMax);
110993
110994         me.store.filterBy(function(record) {
110995             var date = record.get('date');
110996             return date >= min && date <= max;
110997         });
110998     },
110999
111000     /**
111001      * @private
111002      * Creates the internal {@link Ext.data.Store} that contains the available times. The store
111003      * is loaded with all possible times, and it is later filtered to hide those times outside
111004      * the minValue/maxValue.
111005      */
111006     createStore: function() {
111007         var me = this,
111008             utilDate = Ext.Date,
111009             times = [],
111010             min = me.absMin,
111011             max = me.absMax;
111012
111013         while(min <= max){
111014             times.push({
111015                 disp: utilDate.dateFormat(min, me.format),
111016                 date: min
111017             });
111018             min = utilDate.add(min, 'mi', me.increment);
111019         }
111020
111021         return Ext.create('Ext.data.Store', {
111022             fields: ['disp', 'date'],
111023             data: times
111024         });
111025     }
111026
111027 });
111028
111029 /**
111030  * Provides a time input field with a time dropdown and automatic time validation.
111031  *
111032  * This field recognizes and uses JavaScript Date objects as its main {@link #value} type (only the time portion of the
111033  * date is used; the month/day/year are ignored). In addition, it recognizes string values which are parsed according to
111034  * the {@link #format} and/or {@link #altFormats} configs. These may be reconfigured to use time formats appropriate for
111035  * the user's locale.
111036  *
111037  * The field may be limited to a certain range of times by using the {@link #minValue} and {@link #maxValue} configs,
111038  * and the interval between time options in the dropdown can be changed with the {@link #increment} config.
111039  *
111040  * Example usage:
111041  *
111042  *     @example
111043  *     Ext.create('Ext.form.Panel', {
111044  *         title: 'Time Card',
111045  *         width: 300,
111046  *         bodyPadding: 10,
111047  *         renderTo: Ext.getBody(),
111048  *         items: [{
111049  *             xtype: 'timefield',
111050  *             name: 'in',
111051  *             fieldLabel: 'Time In',
111052  *             minValue: '6:00 AM',
111053  *             maxValue: '8:00 PM',
111054  *             increment: 30,
111055  *             anchor: '100%'
111056  *         }, {
111057  *             xtype: 'timefield',
111058  *             name: 'out',
111059  *             fieldLabel: 'Time Out',
111060  *             minValue: '6:00 AM',
111061  *             maxValue: '8:00 PM',
111062  *             increment: 30,
111063  *             anchor: '100%'
111064  *        }]
111065  *     });
111066  */
111067 Ext.define('Ext.form.field.Time', {
111068     extend:'Ext.form.field.Picker',
111069     alias: 'widget.timefield',
111070     requires: ['Ext.form.field.Date', 'Ext.picker.Time', 'Ext.view.BoundListKeyNav', 'Ext.Date'],
111071     alternateClassName: ['Ext.form.TimeField', 'Ext.form.Time'],
111072
111073     /**
111074      * @cfg {String} triggerCls
111075      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
111076      * by default and triggerCls will be **appended** if specified. Defaults to 'x-form-time-trigger' for the Time field
111077      * trigger.
111078      */
111079     triggerCls: Ext.baseCSSPrefix + 'form-time-trigger',
111080
111081     /**
111082      * @cfg {Date/String} minValue
111083      * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
111084      * valid format -- see {@link #format} and {@link #altFormats}.
111085      */
111086
111087     /**
111088      * @cfg {Date/String} maxValue
111089      * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
111090      * valid format -- see {@link #format} and {@link #altFormats}.
111091      */
111092
111093     /**
111094      * @cfg {String} minText
111095      * The error text to display when the entered time is before {@link #minValue}.
111096      */
111097     minText : "The time in this field must be equal to or after {0}",
111098
111099     /**
111100      * @cfg {String} maxText
111101      * The error text to display when the entered time is after {@link #maxValue}.
111102      */
111103     maxText : "The time in this field must be equal to or before {0}",
111104
111105     /**
111106      * @cfg {String} invalidText
111107      * The error text to display when the time in the field is invalid.
111108      */
111109     invalidText : "{0} is not a valid time",
111110
111111     /**
111112      * @cfg {String} format
111113      * The default time format string which can be overriden for localization support. The format must be valid
111114      * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i'
111115      * instead.
111116      */
111117     format : "g:i A",
111118
111119     /**
111120      * @cfg {String} submitFormat
111121      * The date format string which will be submitted to the server. The format must be valid according to {@link
111122      * Ext.Date#parse} (defaults to {@link #format}).
111123      */
111124
111125     /**
111126      * @cfg {String} altFormats
111127      * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
111128      * format.
111129      */
111130     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",
111131
111132     /**
111133      * @cfg {Number} increment
111134      * The number of minutes between each time value in the list.
111135      */
111136     increment: 15,
111137
111138     /**
111139      * @cfg {Number} pickerMaxHeight
111140      * The maximum height of the {@link Ext.picker.Time} dropdown.
111141      */
111142     pickerMaxHeight: 300,
111143
111144     /**
111145      * @cfg {Boolean} selectOnTab
111146      * Whether the Tab key should select the currently highlighted item.
111147      */
111148     selectOnTab: true,
111149
111150     /**
111151      * @private
111152      * This is the date to use when generating time values in the absence of either minValue
111153      * or maxValue.  Using the current date causes DST issues on DST boundary dates, so this is an
111154      * arbitrary "safe" date that can be any date aside from DST boundary dates.
111155      */
111156     initDate: '1/1/2008',
111157     initDateFormat: 'j/n/Y',
111158
111159
111160     initComponent: function() {
111161         var me = this,
111162             min = me.minValue,
111163             max = me.maxValue;
111164         if (min) {
111165             me.setMinValue(min);
111166         }
111167         if (max) {
111168             me.setMaxValue(max);
111169         }
111170         this.callParent();
111171     },
111172
111173     initValue: function() {
111174         var me = this,
111175             value = me.value;
111176
111177         // If a String value was supplied, try to convert it to a proper Date object
111178         if (Ext.isString(value)) {
111179             me.value = me.rawToValue(value);
111180         }
111181
111182         me.callParent();
111183     },
111184
111185     /**
111186      * Replaces any existing {@link #minValue} with the new time and refreshes the picker's range.
111187      * @param {Date/String} value The minimum time that can be selected
111188      */
111189     setMinValue: function(value) {
111190         var me = this,
111191             picker = me.picker;
111192         me.setLimit(value, true);
111193         if (picker) {
111194             picker.setMinValue(me.minValue);
111195         }
111196     },
111197
111198     /**
111199      * Replaces any existing {@link #maxValue} with the new time and refreshes the picker's range.
111200      * @param {Date/String} value The maximum time that can be selected
111201      */
111202     setMaxValue: function(value) {
111203         var me = this,
111204             picker = me.picker;
111205         me.setLimit(value, false);
111206         if (picker) {
111207             picker.setMaxValue(me.maxValue);
111208         }
111209     },
111210
111211     /**
111212      * @private
111213      * Updates either the min or max value. Converts the user's value into a Date object whose
111214      * year/month/day is set to the {@link #initDate} so that only the time fields are significant.
111215      */
111216     setLimit: function(value, isMin) {
111217         var me = this,
111218             d, val;
111219         if (Ext.isString(value)) {
111220             d = me.parseDate(value);
111221         }
111222         else if (Ext.isDate(value)) {
111223             d = value;
111224         }
111225         if (d) {
111226             val = Ext.Date.clearTime(new Date(me.initDate));
111227             val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
111228             me[isMin ? 'minValue' : 'maxValue'] = val;
111229         }
111230     },
111231
111232     rawToValue: function(rawValue) {
111233         return this.parseDate(rawValue) || rawValue || null;
111234     },
111235
111236     valueToRaw: function(value) {
111237         return this.formatDate(this.parseDate(value));
111238     },
111239
111240     /**
111241      * Runs all of Time's validations and returns an array of any errors. Note that this first runs Text's validations,
111242      * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
111243      * the time format is valid, that the chosen time is within the {@link #minValue} and {@link #maxValue} constraints
111244      * set.
111245      * @param {Object} [value] The value to get errors for (defaults to the current field value)
111246      * @return {String[]} All validation errors for this field
111247      */
111248     getErrors: function(value) {
111249         var me = this,
111250             format = Ext.String.format,
111251             errors = me.callParent(arguments),
111252             minValue = me.minValue,
111253             maxValue = me.maxValue,
111254             date;
111255
111256         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
111257
111258         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
111259              return errors;
111260         }
111261
111262         date = me.parseDate(value);
111263         if (!date) {
111264             errors.push(format(me.invalidText, value, me.format));
111265             return errors;
111266         }
111267
111268         if (minValue && date < minValue) {
111269             errors.push(format(me.minText, me.formatDate(minValue)));
111270         }
111271
111272         if (maxValue && date > maxValue) {
111273             errors.push(format(me.maxText, me.formatDate(maxValue)));
111274         }
111275
111276         return errors;
111277     },
111278
111279     formatDate: function() {
111280         return Ext.form.field.Date.prototype.formatDate.apply(this, arguments);
111281     },
111282
111283     /**
111284      * @private
111285      * Parses an input value into a valid Date object.
111286      * @param {String/Date} value
111287      */
111288     parseDate: function(value) {
111289         if (!value || Ext.isDate(value)) {
111290             return value;
111291         }
111292
111293         var me = this,
111294             val = me.safeParse(value, me.format),
111295             altFormats = me.altFormats,
111296             altFormatsArray = me.altFormatsArray,
111297             i = 0,
111298             len;
111299
111300         if (!val && altFormats) {
111301             altFormatsArray = altFormatsArray || altFormats.split('|');
111302             len = altFormatsArray.length;
111303             for (; i < len && !val; ++i) {
111304                 val = me.safeParse(value, altFormatsArray[i]);
111305             }
111306         }
111307         return val;
111308     },
111309
111310     safeParse: function(value, format){
111311         var me = this,
111312             utilDate = Ext.Date,
111313             parsedDate,
111314             result = null;
111315
111316         if (utilDate.formatContainsDateInfo(format)) {
111317             // assume we've been given a full date
111318             result = utilDate.parse(value, format);
111319         } else {
111320             // Use our initial safe date
111321             parsedDate = utilDate.parse(me.initDate + ' ' + value, me.initDateFormat + ' ' + format);
111322             if (parsedDate) {
111323                 result = parsedDate;
111324             }
111325         }
111326         return result;
111327     },
111328
111329     // @private
111330     getSubmitValue: function() {
111331         var me = this,
111332             format = me.submitFormat || me.format,
111333             value = me.getValue();
111334
111335         return value ? Ext.Date.format(value, format) : null;
111336     },
111337
111338     /**
111339      * @private
111340      * Creates the {@link Ext.picker.Time}
111341      */
111342     createPicker: function() {
111343         var me = this,
111344             picker = Ext.create('Ext.picker.Time', {
111345                 pickerField: me,
111346                 selModel: {
111347                     mode: 'SINGLE'
111348                 },
111349                 floating: true,
111350                 hidden: true,
111351                 minValue: me.minValue,
111352                 maxValue: me.maxValue,
111353                 increment: me.increment,
111354                 format: me.format,
111355                 ownerCt: this.ownerCt,
111356                 renderTo: document.body,
111357                 maxHeight: me.pickerMaxHeight,
111358                 focusOnToFront: false
111359             });
111360
111361         me.mon(picker.getSelectionModel(), {
111362             selectionchange: me.onListSelect,
111363             scope: me
111364         });
111365
111366         return picker;
111367     },
111368
111369     /**
111370      * @private
111371      * Enables the key nav for the Time picker when it is expanded.
111372      * TODO this is largely the same logic as ComboBox, should factor out.
111373      */
111374     onExpand: function() {
111375         var me = this,
111376             keyNav = me.pickerKeyNav,
111377             selectOnTab = me.selectOnTab,
111378             picker = me.getPicker(),
111379             lastSelected = picker.getSelectionModel().lastSelected,
111380             itemNode;
111381
111382         if (!keyNav) {
111383             keyNav = me.pickerKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
111384                 boundList: picker,
111385                 forceKeyDown: true,
111386                 tab: function(e) {
111387                     if (selectOnTab) {
111388                         if(me.picker.highlightedItem) {
111389                             this.selectHighlighted(e);
111390                         } else {
111391                             me.collapse();
111392                         }
111393                         me.triggerBlur();
111394                     }
111395                     // Tab key event is allowed to propagate to field
111396                     return true;
111397                 }
111398             });
111399             // stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
111400             if (selectOnTab) {
111401                 me.ignoreMonitorTab = true;
111402             }
111403         }
111404         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
111405
111406         // Highlight the last selected item and scroll it into view
111407         if (lastSelected) {
111408             itemNode = picker.getNode(lastSelected);
111409             if (itemNode) {
111410                 picker.highlightItem(itemNode);
111411                 picker.el.scrollChildIntoView(itemNode, false);
111412             }
111413         }
111414     },
111415
111416     /**
111417      * @private
111418      * Disables the key nav for the Time picker when it is collapsed.
111419      */
111420     onCollapse: function() {
111421         var me = this,
111422             keyNav = me.pickerKeyNav;
111423         if (keyNav) {
111424             keyNav.disable();
111425             me.ignoreMonitorTab = false;
111426         }
111427     },
111428
111429     /**
111430      * @private
111431      * Clears the highlighted item in the picker on change.
111432      * This prevents the highlighted item from being selected instead of the custom typed in value when the tab key is pressed.
111433      */
111434     onChange: function() {
111435         var me = this,
111436             picker = me.picker;
111437
111438         me.callParent(arguments);
111439         if(picker) {
111440             picker.clearHighlight();
111441         }
111442     },
111443
111444     /**
111445      * @private
111446      * Handles a time being selected from the Time picker.
111447      */
111448     onListSelect: function(list, recordArray) {
111449         var me = this,
111450             record = recordArray[0],
111451             val = record ? record.get('date') : null;
111452         me.setValue(val);
111453         me.fireEvent('select', me, val);
111454         me.picker.clearHighlight();
111455         me.collapse();
111456         me.inputEl.focus();
111457     }
111458 });
111459
111460
111461 /**
111462  * @class Ext.grid.CellEditor
111463  * @extends Ext.Editor
111464  * Internal utility class that provides default configuration for cell editing.
111465  * @ignore
111466  */
111467 Ext.define('Ext.grid.CellEditor', {
111468     extend: 'Ext.Editor',
111469     constructor: function(config) {
111470         config = Ext.apply({}, config);
111471         
111472         if (config.field) {
111473             config.field.monitorTab = false;
111474         }
111475         if (!Ext.isDefined(config.autoSize)) {
111476             config.autoSize = {
111477                 width: 'boundEl'
111478             };
111479         }
111480         this.callParent([config]);
111481     },
111482     
111483     /**
111484      * @private
111485      * Hide the grid cell when editor is shown.
111486      */
111487     onShow: function() {
111488         var first = this.boundEl.first();
111489         if (first) {
111490             first.hide();
111491         }
111492         this.callParent(arguments);
111493     },
111494     
111495     /**
111496      * @private
111497      * Show grid cell when editor is hidden.
111498      */
111499     onHide: function() {
111500         var first = this.boundEl.first();
111501         if (first) {
111502             first.show();
111503         }
111504         this.callParent(arguments);
111505     },
111506     
111507     /**
111508      * @private
111509      * Fix checkbox blur when it is clicked.
111510      */
111511     afterRender: function() {
111512         this.callParent(arguments);
111513         var field = this.field;
111514         if (field.isXType('checkboxfield')) {
111515             field.mon(field.inputEl, 'mousedown', this.onCheckBoxMouseDown, this);
111516             field.mon(field.inputEl, 'click', this.onCheckBoxClick, this);
111517         }
111518     },
111519     
111520     /**
111521      * @private
111522      * Because when checkbox is clicked it loses focus  completeEdit is bypassed.
111523      */
111524     onCheckBoxMouseDown: function() {
111525         this.completeEdit = Ext.emptyFn;
111526     },
111527     
111528     /**
111529      * @private
111530      * Restore checkbox focus and completeEdit method.
111531      */
111532     onCheckBoxClick: function() {
111533         delete this.completeEdit;
111534         this.field.focus(false, 10);
111535     },
111536     
111537     alignment: "tl-tl",
111538     hideEl : false,
111539     cls: Ext.baseCSSPrefix + "small-editor " + Ext.baseCSSPrefix + "grid-editor",
111540     shim: false,
111541     shadow: false
111542 });
111543 /**
111544  * @class Ext.grid.ColumnLayout
111545  * @extends Ext.layout.container.HBox
111546  * @private
111547  *
111548  * <p>This class is used only by the grid's HeaderContainer docked child.</p>
111549  *
111550  * <p>It adds the ability to shrink the vertical size of the inner container element back if a grouped
111551  * column header has all its child columns dragged out, and the whole HeaderContainer needs to shrink back down.</p>
111552  *
111553  * <p>Also, after every layout, after all headers have attained their 'stretchmax' height, it goes through and calls
111554  * <code>setPadding</code> on the columns so that they lay out correctly.</p>
111555  */
111556 Ext.define('Ext.grid.ColumnLayout', {
111557     extend: 'Ext.layout.container.HBox',
111558     alias: 'layout.gridcolumn',
111559     type : 'column',
111560
111561     reserveOffset: false,
111562
111563     shrinkToFit: false,
111564
111565     // Height-stretched innerCt must be able to revert back to unstretched height
111566     clearInnerCtOnLayout: true,
111567
111568     beforeLayout: function() {
111569         var me = this,
111570             i = 0,
111571             items = me.getLayoutItems(),
111572             len = items.length,
111573             item, returnValue,
111574             s;
111575
111576         // Scrollbar offset defined by width of any vertical scroller in the owning grid
111577         if (!Ext.isDefined(me.availableSpaceOffset)) {
111578             s = me.owner.up('tablepanel').verticalScroller;
111579             me.availableSpaceOffset = s ? s.width-1 : 0;
111580         }
111581
111582         returnValue = me.callParent(arguments);
111583
111584         // Size to a sane minimum height before possibly being stretched to accommodate grouped headers
111585         me.innerCt.setHeight(23);
111586
111587         // Unstretch child items before the layout which stretches them.
111588         for (; i < len; i++) {
111589             item = items[i];
111590             item.el.setStyle({
111591                 height: 'auto'
111592             });
111593             item.titleContainer.setStyle({
111594                 height: 'auto',
111595                 paddingTop: '0'
111596             });
111597             if (item.componentLayout && item.componentLayout.lastComponentSize) {
111598                 item.componentLayout.lastComponentSize.height = item.el.dom.offsetHeight;
111599             }
111600         }
111601         return returnValue;
111602     },
111603
111604     // Override to enforce the forceFit config.
111605     calculateChildBoxes: function(visibleItems, targetSize) {
111606         var me = this,
111607             calculations = me.callParent(arguments),
111608             boxes = calculations.boxes,
111609             metaData = calculations.meta,
111610             len = boxes.length, i = 0, box, item;
111611
111612         if (targetSize.width && !me.isHeader) {
111613             // If configured forceFit then all columns will be flexed
111614             if (me.owner.forceFit) {
111615
111616                 for (; i < len; i++) {
111617                     box = boxes[i];
111618                     item = box.component;
111619
111620                     // Set a sane minWidth for the Box layout to be able to squeeze flexed Headers down to.
111621                     item.minWidth = Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
111622
111623                     // For forceFit, just use allocated width as the flex value, and the proportions
111624                     // will end up the same whatever HeaderContainer width they are being forced into.
111625                     item.flex = box.width;
111626                 }
111627
111628                 // Recalculate based upon all columns now being flexed instead of sized.
111629                 calculations = me.callParent(arguments);
111630             }
111631             else if (metaData.tooNarrow) {
111632                 targetSize.width = metaData.desiredSize;
111633             }
111634         }
111635
111636         return calculations;
111637     },
111638
111639     afterLayout: function() {
111640         var me = this,
111641             owner = me.owner,
111642             topGrid,
111643             bothHeaderCts,
111644             otherHeaderCt,
111645             thisHeight,
111646             otherHeight,
111647             modifiedGrid,
111648             i = 0,
111649             items,
111650             len,
111651             headerHeight;
111652
111653         me.callParent(arguments);
111654
111655         // Set up padding in items
111656         if (!me.owner.hideHeaders) {
111657
111658             // If this is one HeaderContainer of a pair in a side-by-side locking view, then find the height
111659             // of the highest one, and sync the other one to that height.
111660             if (owner.lockableInjected) {
111661                 topGrid = owner.up('tablepanel').up('tablepanel');
111662                 bothHeaderCts = topGrid.query('headercontainer:not([isHeader])');
111663                 otherHeaderCt = (bothHeaderCts[0] === owner) ? bothHeaderCts[1] : bothHeaderCts[0];
111664
111665                 // Both sides must be rendered for this syncing operation to work.
111666                 if (!otherHeaderCt.rendered) {
111667                     return;
111668                 }
111669
111670                 // Get the height of the highest of both HeaderContainers
111671                 otherHeight = otherHeaderCt.layout.getRenderTarget().getViewSize().height;
111672                 if (!otherHeight) {
111673                     return;
111674                 }
111675                 thisHeight = this.getRenderTarget().getViewSize().height;
111676                 if (!thisHeight) {
111677                     return;
111678                 }
111679
111680                 // Prevent recursion back into here when the "other" grid, after adjusting to the new hight of its headerCt, attempts to inform its ownerCt
111681                 // Block the upward notification by flagging the top grid's component layout as busy.
111682                 topGrid.componentLayout.layoutBusy = true;
111683
111684                 // Assume that the correct header height is the height of this HeaderContainer
111685                 headerHeight = thisHeight;
111686
111687                 // Synch the height of the smaller HeaderContainer to the height of the highest one.
111688                 if (thisHeight > otherHeight) {
111689                     otherHeaderCt.layout.align = 'stretch';
111690                     otherHeaderCt.setCalculatedSize(otherHeaderCt.getWidth(), owner.getHeight(), otherHeaderCt.ownerCt);
111691                     delete otherHeaderCt.layout.align;
111692                     modifiedGrid = otherHeaderCt.up('tablepanel');
111693                 } else if (otherHeight > thisHeight) {
111694                     headerHeight = otherHeight;
111695                     this.align = 'stretch';
111696                     owner.setCalculatedSize(owner.getWidth(), otherHeaderCt.getHeight(), owner.ownerCt);
111697                     delete this.align;
111698                     modifiedGrid = owner.up('tablepanel');
111699                 }
111700                 topGrid.componentLayout.layoutBusy = false;
111701
111702                 // Gather all Header items across both Grids.
111703                 items = bothHeaderCts[0].layout.getLayoutItems().concat(bothHeaderCts[1].layout.getLayoutItems());
111704             } else {
111705                 headerHeight = this.getRenderTarget().getViewSize().height;
111706                 items = me.getLayoutItems();
111707             }
111708
111709             len = items.length;
111710             for (; i < len; i++) {
111711                 items[i].setPadding(headerHeight);
111712             }
111713
111714             // Size the View within the grid which has had its HeaderContainer entallened (That's a perfectly cromulent word BTW)
111715             if (modifiedGrid) {
111716                 setTimeout(function() {
111717                     modifiedGrid.doLayout();
111718                 }, 1);
111719             }
111720         }
111721     },
111722
111723     // FIX: when flexing we actually don't have enough space as we would
111724     // typically because of the scrollOffset on the GridView, must reserve this
111725     updateInnerCtSize: function(tSize, calcs) {
111726         var me = this,
111727             extra;
111728
111729         // Columns must not account for scroll offset
111730         if (!me.isHeader) {
111731             me.tooNarrow = calcs.meta.tooNarrow;
111732             extra = (me.reserveOffset ? me.availableSpaceOffset : 0);
111733
111734             if (calcs.meta.tooNarrow) {
111735                 tSize.width = calcs.meta.desiredSize + extra;
111736             } else {
111737                 tSize.width += extra;
111738             }
111739         }
111740
111741         return me.callParent(arguments);
111742     },
111743
111744     doOwnerCtLayouts: function() {
111745         var ownerCt = this.owner.ownerCt;
111746         if (!ownerCt.componentLayout.layoutBusy) {
111747             ownerCt.doComponentLayout();
111748         }
111749     }
111750 });
111751 /**
111752  * @class Ext.grid.LockingView
111753  * This class is used internally to provide a single interface when using
111754  * a locking grid. Internally, the locking grid creates two separate grids,
111755  * so this class is used to map calls appropriately.
111756  * @ignore
111757  */
111758 Ext.define('Ext.grid.LockingView', {
111759
111760     mixins: {
111761         observable: 'Ext.util.Observable'
111762     },
111763
111764     eventRelayRe: /^(beforeitem|beforecontainer|item|container|cell)/,
111765
111766     constructor: function(config){
111767         var me = this,
111768             eventNames = [],
111769             eventRe = me.eventRelayRe,
111770             locked = config.locked.getView(),
111771             normal = config.normal.getView(),
111772             events,
111773             event;
111774
111775         Ext.apply(me, {
111776             lockedView: locked,
111777             normalView: normal,
111778             lockedGrid: config.locked,
111779             normalGrid: config.normal,
111780             panel: config.panel
111781         });
111782         me.mixins.observable.constructor.call(me, config);
111783
111784         // relay events
111785         events = locked.events;
111786         for (event in events) {
111787             if (events.hasOwnProperty(event) && eventRe.test(event)) {
111788                 eventNames.push(event);
111789             }
111790         }
111791         me.relayEvents(locked, eventNames);
111792         me.relayEvents(normal, eventNames);
111793
111794         normal.on({
111795             scope: me,
111796             itemmouseleave: me.onItemMouseLeave,
111797             itemmouseenter: me.onItemMouseEnter
111798         });
111799
111800         locked.on({
111801             scope: me,
111802             itemmouseleave: me.onItemMouseLeave,
111803             itemmouseenter: me.onItemMouseEnter
111804         });
111805     },
111806
111807     getGridColumns: function() {
111808         var cols = this.lockedGrid.headerCt.getGridColumns();
111809         return cols.concat(this.normalGrid.headerCt.getGridColumns());
111810     },
111811
111812     getEl: function(column){
111813         return this.getViewForColumn(column).getEl();
111814     },
111815
111816     getViewForColumn: function(column) {
111817         var view = this.lockedView,
111818             inLocked;
111819
111820         view.headerCt.cascade(function(col){
111821             if (col === column) {
111822                 inLocked = true;
111823                 return false;
111824             }
111825         });
111826
111827         return inLocked ? view : this.normalView;
111828     },
111829
111830     onItemMouseEnter: function(view, record){
111831         var me = this,
111832             locked = me.lockedView,
111833             other = me.normalView,
111834             item;
111835
111836         if (view.trackOver) {
111837             if (view !== locked) {
111838                 other = locked;
111839             }
111840             item = other.getNode(record);
111841             other.highlightItem(item);
111842         }
111843     },
111844
111845     onItemMouseLeave: function(view, record){
111846         var me = this,
111847             locked = me.lockedView,
111848             other = me.normalView;
111849
111850         if (view.trackOver) {
111851             if (view !== locked) {
111852                 other = locked;
111853             }
111854             other.clearHighlight();
111855         }
111856     },
111857
111858     relayFn: function(name, args){
111859         args = args || [];
111860
111861         var view = this.lockedView;
111862         view[name].apply(view, args || []);
111863         view = this.normalView;
111864         view[name].apply(view, args || []);
111865     },
111866
111867     getSelectionModel: function(){
111868         return this.panel.getSelectionModel();
111869     },
111870
111871     getStore: function(){
111872         return this.panel.store;
111873     },
111874
111875     getNode: function(nodeInfo){
111876         // default to the normal view
111877         return this.normalView.getNode(nodeInfo);
111878     },
111879
111880     getCell: function(record, column){
111881         var view = this.getViewForColumn(column),
111882             row;
111883
111884         row = view.getNode(record);
111885         return Ext.fly(row).down(column.getCellSelector());
111886     },
111887
111888     getRecord: function(node){
111889         var result = this.lockedView.getRecord(node);
111890         if (!node) {
111891             result = this.normalView.getRecord(node);
111892         }
111893         return result;
111894     },
111895
111896     addElListener: function(eventName, fn, scope){
111897         this.relayFn('addElListener', arguments);
111898     },
111899
111900     refreshNode: function(){
111901         this.relayFn('refreshNode', arguments);
111902     },
111903
111904     refresh: function(){
111905         this.relayFn('refresh', arguments);
111906     },
111907
111908     bindStore: function(){
111909         this.relayFn('bindStore', arguments);
111910     },
111911
111912     addRowCls: function(){
111913         this.relayFn('addRowCls', arguments);
111914     },
111915
111916     removeRowCls: function(){
111917         this.relayFn('removeRowCls', arguments);
111918     }
111919
111920 });
111921 /**
111922  * @class Ext.grid.Lockable
111923  * @private
111924  *
111925  * Lockable is a private mixin which injects lockable behavior into any
111926  * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
111927  * automatically inject the Ext.grid.Lockable mixin in when one of the
111928  * these conditions are met:
111929  *
111930  *  - The TablePanel has the lockable configuration set to true
111931  *  - One of the columns in the TablePanel has locked set to true/false
111932  *
111933  * Each TablePanel subclass must register an alias. It should have an array
111934  * of configurations to copy to the 2 separate tablepanel's that will be generated
111935  * to note what configurations should be copied. These are named normalCfgCopy and
111936  * lockedCfgCopy respectively.
111937  *
111938  * Columns which are locked must specify a fixed width. They do NOT support a
111939  * flex width.
111940  *
111941  * Configurations which are specified in this class will be available on any grid or
111942  * tree which is using the lockable functionality.
111943  */
111944 Ext.define('Ext.grid.Lockable', {
111945
111946     requires: ['Ext.grid.LockingView'],
111947
111948     /**
111949      * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
111950      * locked grid view. This is turned on by default. If your grid is guaranteed
111951      * to have rows of all the same height, you should set this to false to
111952      * optimize performance.
111953      */
111954     syncRowHeight: true,
111955
111956     /**
111957      * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
111958      * not specified lockable will determine the subgrid xtype to create by the
111959      * following rule. Use the superclasses xtype if the superclass is NOT
111960      * tablepanel, otherwise use the xtype itself.
111961      */
111962
111963     /**
111964      * @cfg {Object} lockedViewConfig A view configuration to be applied to the
111965      * locked side of the grid. Any conflicting configurations between lockedViewConfig
111966      * and viewConfig will be overwritten by the lockedViewConfig.
111967      */
111968
111969     /**
111970      * @cfg {Object} normalViewConfig A view configuration to be applied to the
111971      * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
111972      * and viewConfig will be overwritten by the normalViewConfig.
111973      */
111974
111975     // private variable to track whether or not the spacer is hidden/visible
111976     spacerHidden: true,
111977
111978     headerCounter: 0,
111979
111980     // i8n text
111981     unlockText: 'Unlock',
111982     lockText: 'Lock',
111983
111984     determineXTypeToCreate: function() {
111985         var me = this,
111986             typeToCreate;
111987
111988         if (me.subGridXType) {
111989             typeToCreate = me.subGridXType;
111990         } else {
111991             var xtypes     = this.getXTypes().split('/'),
111992                 xtypesLn   = xtypes.length,
111993                 xtype      = xtypes[xtypesLn - 1],
111994                 superxtype = xtypes[xtypesLn - 2];
111995
111996             if (superxtype !== 'tablepanel') {
111997                 typeToCreate = superxtype;
111998             } else {
111999                 typeToCreate = xtype;
112000             }
112001         }
112002
112003         return typeToCreate;
112004     },
112005
112006     // injectLockable will be invoked before initComponent's parent class implementation
112007     // is called, so throughout this method this. are configurations
112008     injectLockable: function() {
112009         // ensure lockable is set to true in the TablePanel
112010         this.lockable = true;
112011         // Instruct the TablePanel it already has a view and not to create one.
112012         // We are going to aggregate 2 copies of whatever TablePanel we are using
112013         this.hasView = true;
112014
112015         var me = this,
112016             // xtype of this class, 'treepanel' or 'gridpanel'
112017             // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
112018             // alias.)
112019             xtype = me.determineXTypeToCreate(),
112020             // share the selection model
112021             selModel = me.getSelectionModel(),
112022             lockedGrid = {
112023                 xtype: xtype,
112024                 // Lockable does NOT support animations for Tree
112025                 enableAnimations: false,
112026                 scroll: false,
112027                 scrollerOwner: false,
112028                 selModel: selModel,
112029                 border: false,
112030                 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
112031             },
112032             normalGrid = {
112033                 xtype: xtype,
112034                 enableAnimations: false,
112035                 scrollerOwner: false,
112036                 selModel: selModel,
112037                 border: false
112038             },
112039             i = 0,
112040             columns,
112041             lockedHeaderCt,
112042             normalHeaderCt;
112043
112044         me.addCls(Ext.baseCSSPrefix + 'grid-locked');
112045
112046         // copy appropriate configurations to the respective
112047         // aggregated tablepanel instances and then delete them
112048         // from the master tablepanel.
112049         Ext.copyTo(normalGrid, me, me.normalCfgCopy);
112050         Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
112051         for (; i < me.normalCfgCopy.length; i++) {
112052             delete me[me.normalCfgCopy[i]];
112053         }
112054         for (i = 0; i < me.lockedCfgCopy.length; i++) {
112055             delete me[me.lockedCfgCopy[i]];
112056         }
112057
112058         me.addEvents(
112059             /**
112060              * @event lockcolumn
112061              * Fires when a column is locked.
112062              * @param {Ext.grid.Panel} this The gridpanel.
112063              * @param {Ext.grid.column.Column} column The column being locked.
112064              */
112065             'lockcolumn',
112066
112067             /**
112068              * @event unlockcolumn
112069              * Fires when a column is unlocked.
112070              * @param {Ext.grid.Panel} this The gridpanel.
112071              * @param {Ext.grid.column.Column} column The column being unlocked.
112072              */
112073             'unlockcolumn'
112074         );
112075
112076         me.addStateEvents(['lockcolumn', 'unlockcolumn']);
112077
112078         me.lockedHeights = [];
112079         me.normalHeights = [];
112080
112081         columns = me.processColumns(me.columns);
112082
112083         lockedGrid.width = columns.lockedWidth + Ext.num(selModel.headerWidth, 0);
112084         lockedGrid.columns = columns.locked;
112085         normalGrid.columns = columns.normal;
112086
112087         me.store = Ext.StoreManager.lookup(me.store);
112088         lockedGrid.store = me.store;
112089         normalGrid.store = me.store;
112090
112091         // normal grid should flex the rest of the width
112092         normalGrid.flex = 1;
112093         lockedGrid.viewConfig = me.lockedViewConfig || {};
112094         lockedGrid.viewConfig.loadingUseMsg = false;
112095         normalGrid.viewConfig = me.normalViewConfig || {};
112096
112097         Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
112098         Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
112099
112100         me.normalGrid = Ext.ComponentManager.create(normalGrid);
112101         me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
112102
112103         me.view = Ext.create('Ext.grid.LockingView', {
112104             locked: me.lockedGrid,
112105             normal: me.normalGrid,
112106             panel: me
112107         });
112108
112109         if (me.syncRowHeight) {
112110             me.lockedGrid.getView().on({
112111                 refresh: me.onLockedGridAfterRefresh,
112112                 itemupdate: me.onLockedGridAfterUpdate,
112113                 scope: me
112114             });
112115
112116             me.normalGrid.getView().on({
112117                 refresh: me.onNormalGridAfterRefresh,
112118                 itemupdate: me.onNormalGridAfterUpdate,
112119                 scope: me
112120             });
112121         }
112122
112123         lockedHeaderCt = me.lockedGrid.headerCt;
112124         normalHeaderCt = me.normalGrid.headerCt;
112125
112126         lockedHeaderCt.lockedCt = true;
112127         lockedHeaderCt.lockableInjected = true;
112128         normalHeaderCt.lockableInjected = true;
112129
112130         lockedHeaderCt.on({
112131             columnshow: me.onLockedHeaderShow,
112132             columnhide: me.onLockedHeaderHide,
112133             columnmove: me.onLockedHeaderMove,
112134             sortchange: me.onLockedHeaderSortChange,
112135             columnresize: me.onLockedHeaderResize,
112136             scope: me
112137         });
112138
112139         normalHeaderCt.on({
112140             columnmove: me.onNormalHeaderMove,
112141             sortchange: me.onNormalHeaderSortChange,
112142             scope: me
112143         });
112144
112145         me.normalGrid.on({
112146             scrollershow: me.onScrollerShow,
112147             scrollerhide: me.onScrollerHide,
112148             scope: me
112149         });
112150
112151         me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
112152
112153         me.modifyHeaderCt();
112154         me.items = [me.lockedGrid, me.normalGrid];
112155
112156         me.relayHeaderCtEvents(lockedHeaderCt);
112157         me.relayHeaderCtEvents(normalHeaderCt);
112158
112159         me.layout = {
112160             type: 'hbox',
112161             align: 'stretch'
112162         };
112163     },
112164
112165     processColumns: function(columns){
112166         // split apart normal and lockedWidths
112167         var i = 0,
112168             len = columns.length,
112169             lockedWidth = 1,
112170             lockedHeaders = [],
112171             normalHeaders = [],
112172             column;
112173
112174         for (; i < len; ++i) {
112175             column = columns[i];
112176             // mark the column as processed so that the locked attribute does not
112177             // trigger trying to aggregate the columns again.
112178             column.processed = true;
112179             if (column.locked) {
112180                 if (column.flex) {
112181                     Ext.Error.raise("Columns which are locked do NOT support a flex width. You must set a width on the " + columns[i].text + "column.");
112182                 }
112183                 if (!column.hidden) {
112184                     lockedWidth += column.width || Ext.grid.header.Container.prototype.defaultWidth;
112185                 }
112186                 lockedHeaders.push(column);
112187             } else {
112188                 normalHeaders.push(column);
112189             }
112190             if (!column.headerId) {
112191                 column.headerId = (column.initialConfig || column).id || ('L' + (++this.headerCounter));
112192             }
112193         }
112194         return {
112195             lockedWidth: lockedWidth,
112196             locked: lockedHeaders,
112197             normal: normalHeaders
112198         };
112199     },
112200
112201     // create a new spacer after the table is refreshed
112202     onLockedGridAfterLayout: function() {
112203         var me         = this,
112204             lockedView = me.lockedGrid.getView();
112205         lockedView.on({
112206             beforerefresh: me.destroySpacer,
112207             scope: me
112208         });
112209     },
112210
112211     // trigger a pseudo refresh on the normal side
112212     onLockedHeaderMove: function() {
112213         if (this.syncRowHeight) {
112214             this.onNormalGridAfterRefresh();
112215         }
112216     },
112217
112218     // trigger a pseudo refresh on the locked side
112219     onNormalHeaderMove: function() {
112220         if (this.syncRowHeight) {
112221             this.onLockedGridAfterRefresh();
112222         }
112223     },
112224
112225     // create a spacer in lockedsection and store a reference
112226     // TODO: Should destroy before refreshing content
112227     getSpacerEl: function() {
112228         var me   = this,
112229             w,
112230             view,
112231             el;
112232
112233         if (!me.spacerEl) {
112234             // This affects scrolling all the way to the bottom of a locked grid
112235             // additional test, sort a column and make sure it synchronizes
112236             w    = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0);
112237             view = me.lockedGrid.getView();
112238             el   = view.el;
112239
112240             me.spacerEl = Ext.DomHelper.append(el, {
112241                 cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
112242                 style: 'height: ' + w + 'px;'
112243             }, true);
112244         }
112245         return me.spacerEl;
112246     },
112247
112248     destroySpacer: function() {
112249         var me = this;
112250         if (me.spacerEl) {
112251             me.spacerEl.destroy();
112252             delete me.spacerEl;
112253         }
112254     },
112255
112256     // cache the heights of all locked rows and sync rowheights
112257     onLockedGridAfterRefresh: function() {
112258         var me     = this,
112259             view   = me.lockedGrid.getView(),
112260             el     = view.el,
112261             rowEls = el.query(view.getItemSelector()),
112262             ln     = rowEls.length,
112263             i = 0;
112264
112265         // reset heights each time.
112266         me.lockedHeights = [];
112267
112268         for (; i < ln; i++) {
112269             me.lockedHeights[i] = rowEls[i].clientHeight;
112270         }
112271         me.syncRowHeights();
112272     },
112273
112274     // cache the heights of all normal rows and sync rowheights
112275     onNormalGridAfterRefresh: function() {
112276         var me     = this,
112277             view   = me.normalGrid.getView(),
112278             el     = view.el,
112279             rowEls = el.query(view.getItemSelector()),
112280             ln     = rowEls.length,
112281             i = 0;
112282
112283         // reset heights each time.
112284         me.normalHeights = [];
112285
112286         for (; i < ln; i++) {
112287             me.normalHeights[i] = rowEls[i].clientHeight;
112288         }
112289         me.syncRowHeights();
112290     },
112291
112292     // rows can get bigger/smaller
112293     onLockedGridAfterUpdate: function(record, index, node) {
112294         this.lockedHeights[index] = node.clientHeight;
112295         this.syncRowHeights();
112296     },
112297
112298     // rows can get bigger/smaller
112299     onNormalGridAfterUpdate: function(record, index, node) {
112300         this.normalHeights[index] = node.clientHeight;
112301         this.syncRowHeights();
112302     },
112303
112304     // match the rowheights to the biggest rowheight on either
112305     // side
112306     syncRowHeights: function() {
112307         var me = this,
112308             lockedHeights = me.lockedHeights,
112309             normalHeights = me.normalHeights,
112310             calcHeights   = [],
112311             ln = lockedHeights.length,
112312             i  = 0,
112313             lockedView, normalView,
112314             lockedRowEls, normalRowEls,
112315             vertScroller = me.getVerticalScroller(),
112316             scrollTop;
112317
112318         // ensure there are an equal num of locked and normal
112319         // rows before synchronization
112320         if (lockedHeights.length && normalHeights.length) {
112321             lockedView = me.lockedGrid.getView();
112322             normalView = me.normalGrid.getView();
112323             lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
112324             normalRowEls = normalView.el.query(normalView.getItemSelector());
112325
112326             // loop thru all of the heights and sync to the other side
112327             for (; i < ln; i++) {
112328                 // ensure both are numbers
112329                 if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
112330                     if (lockedHeights[i] > normalHeights[i]) {
112331                         Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
112332                     } else if (lockedHeights[i] < normalHeights[i]) {
112333                         Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
112334                     }
112335                 }
112336             }
112337
112338             // invalidate the scroller and sync the scrollers
112339             me.normalGrid.invalidateScroller();
112340
112341             // synchronize the view with the scroller, if we have a virtualScrollTop
112342             // then the user is using a PagingScroller
112343             if (vertScroller && vertScroller.setViewScrollTop) {
112344                 vertScroller.setViewScrollTop(me.virtualScrollTop);
112345             } else {
112346                 // We don't use setScrollTop here because if the scrollTop is
112347                 // set to the exact same value some browsers won't fire the scroll
112348                 // event. Instead, we directly set the scrollTop.
112349                 scrollTop = normalView.el.dom.scrollTop;
112350                 normalView.el.dom.scrollTop = scrollTop;
112351                 lockedView.el.dom.scrollTop = scrollTop;
112352             }
112353
112354             // reset the heights
112355             me.lockedHeights = [];
112356             me.normalHeights = [];
112357         }
112358     },
112359
112360     // track when scroller is shown
112361     onScrollerShow: function(scroller, direction) {
112362         if (direction === 'horizontal') {
112363             this.spacerHidden = false;
112364             this.getSpacerEl().removeCls(Ext.baseCSSPrefix + 'hidden');
112365         }
112366     },
112367
112368     // track when scroller is hidden
112369     onScrollerHide: function(scroller, direction) {
112370         if (direction === 'horizontal') {
112371             this.spacerHidden = true;
112372             if (this.spacerEl) {
112373                 this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
112374             }
112375         }
112376     },
112377
112378
112379     // inject Lock and Unlock text
112380     modifyHeaderCt: function() {
112381         var me = this;
112382         me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
112383         me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
112384     },
112385
112386     onUnlockMenuClick: function() {
112387         this.unlock();
112388     },
112389
112390     onLockMenuClick: function() {
112391         this.lock();
112392     },
112393
112394     getMenuItems: function(locked) {
112395         var me            = this,
112396             unlockText    = me.unlockText,
112397             lockText      = me.lockText,
112398             unlockCls     = Ext.baseCSSPrefix + 'hmenu-unlock',
112399             lockCls       = Ext.baseCSSPrefix + 'hmenu-lock',
112400             unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
112401             lockHandler   = Ext.Function.bind(me.onLockMenuClick, me);
112402
112403         // runs in the scope of headerCt
112404         return function() {
112405             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
112406             o.push('-',{
112407                 cls: unlockCls,
112408                 text: unlockText,
112409                 handler: unlockHandler,
112410                 disabled: !locked
112411             });
112412             o.push({
112413                 cls: lockCls,
112414                 text: lockText,
112415                 handler: lockHandler,
112416                 disabled: locked
112417             });
112418             return o;
112419         };
112420     },
112421
112422     // going from unlocked section to locked
112423     /**
112424      * Locks the activeHeader as determined by which menu is open OR a header
112425      * as specified.
112426      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
112427      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to appending as the last item.
112428      * @private
112429      */
112430     lock: function(activeHd, toIdx) {
112431         var me         = this,
112432             normalGrid = me.normalGrid,
112433             lockedGrid = me.lockedGrid,
112434             normalHCt  = normalGrid.headerCt,
112435             lockedHCt  = lockedGrid.headerCt;
112436
112437         activeHd = activeHd || normalHCt.getMenu().activeHeader;
112438
112439         // if column was previously flexed, get/set current width
112440         // and remove the flex
112441         if (activeHd.flex) {
112442             activeHd.width = activeHd.getWidth();
112443             delete activeHd.flex;
112444         }
112445
112446         normalHCt.remove(activeHd, false);
112447         lockedHCt.suspendLayout = true;
112448         activeHd.locked = true;
112449         if (Ext.isDefined(toIdx)) {
112450             lockedHCt.insert(toIdx, activeHd);
112451         } else {
112452             lockedHCt.add(activeHd);
112453         }
112454         lockedHCt.suspendLayout = false;
112455         me.syncLockedSection();
112456
112457         me.fireEvent('lockcolumn', me, activeHd);
112458     },
112459
112460     syncLockedSection: function() {
112461         var me = this;
112462         me.syncLockedWidth();
112463         me.lockedGrid.getView().refresh();
112464         me.normalGrid.getView().refresh();
112465     },
112466
112467     // adjust the locked section to the width of its respective
112468     // headerCt
112469     syncLockedWidth: function() {
112470         var me = this,
112471             width = me.lockedGrid.headerCt.getFullWidth(true);
112472         me.lockedGrid.setWidth(width+1); // +1 for border pixel
112473         me.doComponentLayout();
112474     },
112475
112476     onLockedHeaderResize: function() {
112477         this.syncLockedWidth();
112478     },
112479
112480     onLockedHeaderHide: function() {
112481         this.syncLockedWidth();
112482     },
112483
112484     onLockedHeaderShow: function() {
112485         this.syncLockedWidth();
112486     },
112487
112488     onLockedHeaderSortChange: function(headerCt, header, sortState) {
112489         if (sortState) {
112490             // no real header, and silence the event so we dont get into an
112491             // infinite loop
112492             this.normalGrid.headerCt.clearOtherSortStates(null, true);
112493         }
112494     },
112495
112496     onNormalHeaderSortChange: function(headerCt, header, sortState) {
112497         if (sortState) {
112498             // no real header, and silence the event so we dont get into an
112499             // infinite loop
112500             this.lockedGrid.headerCt.clearOtherSortStates(null, true);
112501         }
112502     },
112503
112504     // going from locked section to unlocked
112505     /**
112506      * Unlocks the activeHeader as determined by which menu is open OR a header
112507      * as specified.
112508      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
112509      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to 0.
112510      * @private
112511      */
112512     unlock: function(activeHd, toIdx) {
112513         var me         = this,
112514             normalGrid = me.normalGrid,
112515             lockedGrid = me.lockedGrid,
112516             normalHCt  = normalGrid.headerCt,
112517             lockedHCt  = lockedGrid.headerCt;
112518
112519         if (!Ext.isDefined(toIdx)) {
112520             toIdx = 0;
112521         }
112522         activeHd = activeHd || lockedHCt.getMenu().activeHeader;
112523
112524         lockedHCt.remove(activeHd, false);
112525         me.syncLockedWidth();
112526         me.lockedGrid.getView().refresh();
112527         activeHd.locked = false;
112528         normalHCt.insert(toIdx, activeHd);
112529         me.normalGrid.getView().refresh();
112530
112531         me.fireEvent('unlockcolumn', me, activeHd);
112532     },
112533
112534     applyColumnsState: function (columns) {
112535         var me = this,
112536             lockedGrid = me.lockedGrid,
112537             lockedHeaderCt = lockedGrid.headerCt,
112538             normalHeaderCt = me.normalGrid.headerCt,
112539             lockedCols = lockedHeaderCt.items,
112540             normalCols = normalHeaderCt.items,
112541             existing,
112542             locked = [],
112543             normal = [],
112544             lockedDefault,
112545             lockedWidth = 1;
112546
112547         Ext.each(columns, function (col) {
112548             function matches (item) {
112549                 return item.headerId == col.id;
112550             }
112551
112552             lockedDefault = true;
112553             if (!(existing = lockedCols.findBy(matches))) {
112554                 existing = normalCols.findBy(matches);
112555                 lockedDefault = false;
112556             }
112557
112558             if (existing) {
112559                 if (existing.applyColumnState) {
112560                     existing.applyColumnState(col);
112561                 }
112562                 if (!Ext.isDefined(existing.locked)) {
112563                     existing.locked = lockedDefault;
112564                 }
112565                 if (existing.locked) {
112566                     locked.push(existing);
112567                     if (!existing.hidden && Ext.isNumber(existing.width)) {
112568                         lockedWidth += existing.width;
112569                     }
112570                 } else {
112571                     normal.push(existing);
112572                 }
112573             }
112574         });
112575
112576         // state and config must have the same columns (compare counts for now):
112577         if (locked.length + normal.length == lockedCols.getCount() + normalCols.getCount()) {
112578             lockedHeaderCt.removeAll(false);
112579             normalHeaderCt.removeAll(false);
112580
112581             lockedHeaderCt.add(locked);
112582             normalHeaderCt.add(normal);
112583
112584             lockedGrid.setWidth(lockedWidth);
112585         }
112586     },
112587
112588     getColumnsState: function () {
112589         var me = this,
112590             locked = me.lockedGrid.headerCt.getColumnsState(),
112591             normal = me.normalGrid.headerCt.getColumnsState();
112592
112593         return locked.concat(normal);
112594     },
112595
112596     // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
112597     reconfigureLockable: function(store, columns) {
112598         var me = this,
112599             lockedGrid = me.lockedGrid,
112600             normalGrid = me.normalGrid;
112601
112602         if (columns) {
112603             lockedGrid.headerCt.suspendLayout = true;
112604             normalGrid.headerCt.suspendLayout = true;
112605             lockedGrid.headerCt.removeAll();
112606             normalGrid.headerCt.removeAll();
112607
112608             columns = me.processColumns(columns);
112609             lockedGrid.setWidth(columns.lockedWidth);
112610             lockedGrid.headerCt.add(columns.locked);
112611             normalGrid.headerCt.add(columns.normal);
112612         }
112613
112614         if (store) {
112615             store = Ext.data.StoreManager.lookup(store);
112616             me.store = store;
112617             lockedGrid.bindStore(store);
112618             normalGrid.bindStore(store);
112619         } else {
112620             lockedGrid.getView().refresh();
112621             normalGrid.getView().refresh();
112622         }
112623
112624         if (columns) {
112625             lockedGrid.headerCt.suspendLayout = false;
112626             normalGrid.headerCt.suspendLayout = false;
112627             lockedGrid.headerCt.forceComponentLayout();
112628             normalGrid.headerCt.forceComponentLayout();
112629         }
112630     }
112631 });
112632
112633 /**
112634  * Docked in an Ext.grid.Panel, controls virtualized scrolling and synchronization
112635  * across different sections.
112636  */
112637 Ext.define('Ext.grid.Scroller', {
112638     extend: 'Ext.Component',
112639     alias: 'widget.gridscroller',
112640     weight: 110,
112641     baseCls: Ext.baseCSSPrefix + 'scroller',
112642     focusable: false,
112643     reservedSpace: 0,
112644
112645     renderTpl: [
112646         '<div class="' + Ext.baseCSSPrefix + 'scroller-ct" id="{baseId}_ct">',
112647             '<div class="' + Ext.baseCSSPrefix + 'stretcher" id="{baseId}_stretch"></div>',
112648         '</div>'
112649     ],
112650
112651     initComponent: function() {
112652         var me       = this,
112653             dock     = me.dock,
112654             cls      = Ext.baseCSSPrefix + 'scroller-vertical';
112655
112656         me.offsets = {bottom: 0};
112657         me.scrollProp = 'scrollTop';
112658         me.vertical = true;
112659         me.sizeProp = 'width';
112660
112661         if (dock === 'top' || dock === 'bottom') {
112662             cls = Ext.baseCSSPrefix + 'scroller-horizontal';
112663             me.sizeProp = 'height';
112664             me.scrollProp = 'scrollLeft';
112665             me.vertical = false;
112666             me.weight += 5;
112667         }
112668
112669         me.cls += (' ' + cls);
112670
112671         Ext.applyIf(me.renderSelectors, {
112672             stretchEl: '.' + Ext.baseCSSPrefix + 'stretcher',
112673             scrollEl: '.' + Ext.baseCSSPrefix + 'scroller-ct'
112674         });
112675         me.callParent();
112676     },
112677     
112678     ensureDimension: function(){
112679         var me = this,
112680             sizeProp = me.sizeProp;
112681             
112682         me[sizeProp] = me.scrollerSize = Ext.getScrollbarSize()[sizeProp];  
112683     },
112684
112685     initRenderData: function () {
112686         var me = this,
112687             ret = me.callParent(arguments) || {};
112688
112689         ret.baseId = me.id;
112690
112691         return ret;
112692     },
112693
112694     afterRender: function() {
112695         var me = this;
112696         me.callParent();
112697         
112698         me.mon(me.scrollEl, 'scroll', me.onElScroll, me);
112699         Ext.cache[me.el.id].skipGarbageCollection = true;
112700     },
112701
112702     onAdded: function(container) {
112703         // Capture the controlling grid Panel so that we can use it even when we are undocked, and don't have an ownerCt
112704         this.ownerGrid = container;
112705         this.callParent(arguments);
112706     },
112707
112708     getSizeCalculation: function() {
112709         var me     = this,
112710             owner  = me.getPanel(),
112711             width  = 1,
112712             height = 1,
112713             view, tbl;
112714
112715         if (!me.vertical) {
112716             // TODO: Must gravitate to a single region..
112717             // Horizontal scrolling only scrolls virtualized region
112718             var items  = owner.query('tableview'),
112719                 center = items[1] || items[0];
112720
112721             if (!center) {
112722                 return false;
112723             }
112724             // center is not guaranteed to have content, such as when there
112725             // are zero rows in the grid/tree. We read the width from the
112726             // headerCt instead.
112727             width = center.headerCt.getFullWidth();
112728
112729             if (Ext.isIEQuirks) {
112730                 width--;
112731             }
112732         } else {
112733             view = owner.down('tableview:not([lockableInjected])');
112734             if (!view || !view.el) {
112735                 return false;
112736             }
112737             tbl = view.el.child('table', true);
112738             if (!tbl) {
112739                 return false;
112740             }
112741
112742             // needs to also account for header and scroller (if still in picture)
112743             // should calculate from headerCt.
112744             height = tbl.offsetHeight;
112745         }
112746         if (isNaN(width)) {
112747             width = 1;
112748         }
112749         if (isNaN(height)) {
112750             height = 1;
112751         }
112752         return {
112753             width: width,
112754             height: height
112755         };
112756     },
112757
112758     invalidate: function(firstPass) {
112759         var me = this,
112760             stretchEl = me.stretchEl;
112761
112762         if (!stretchEl || !me.ownerCt) {
112763             return;
112764         }
112765
112766         var size  = me.getSizeCalculation(),
112767             scrollEl = me.scrollEl,
112768             elDom = scrollEl.dom,
112769             reservedSpace = me.reservedSpace,
112770             pos,
112771             extra = 5;
112772
112773         if (size) {
112774             stretchEl.setSize(size);
112775
112776             size = me.el.getSize(true);
112777
112778             if (me.vertical) {
112779                 size.width += extra;
112780                 size.height -= reservedSpace;
112781                 pos = 'left';
112782             } else {
112783                 size.width -= reservedSpace;
112784                 size.height += extra;
112785                 pos = 'top';
112786             }
112787
112788             scrollEl.setSize(size);
112789             elDom.style[pos] = (-extra) + 'px';
112790
112791             // BrowserBug: IE7
112792             // This makes the scroller enabled, when initially rendering.
112793             elDom.scrollTop = elDom.scrollTop;
112794         }
112795     },
112796
112797     afterComponentLayout: function() {
112798         this.callParent(arguments);
112799         this.invalidate();
112800     },
112801
112802     restoreScrollPos: function () {
112803         var me = this,
112804             el = this.scrollEl,
112805             elDom = el && el.dom;
112806
112807         if (me._scrollPos !== null && elDom) {
112808             elDom[me.scrollProp] = me._scrollPos;
112809             me._scrollPos = null;
112810         }
112811     },
112812
112813     setReservedSpace: function (reservedSpace) {
112814         var me = this;
112815         if (me.reservedSpace !== reservedSpace) {
112816             me.reservedSpace = reservedSpace;
112817             me.invalidate();
112818         }
112819     },
112820
112821     saveScrollPos: function () {
112822         var me = this,
112823             el = this.scrollEl,
112824             elDom = el && el.dom;
112825
112826         me._scrollPos = elDom ? elDom[me.scrollProp] : null;
112827     },
112828
112829     /**
112830      * Sets the scrollTop and constrains the value between 0 and max.
112831      * @param {Number} scrollTop
112832      * @return {Number} The resulting scrollTop value after being constrained
112833      */
112834     setScrollTop: function(scrollTop) {
112835         var el = this.scrollEl,
112836             elDom = el && el.dom;
112837
112838         if (elDom) {
112839             return elDom.scrollTop = Ext.Number.constrain(scrollTop, 0, elDom.scrollHeight - elDom.clientHeight);
112840         }
112841     },
112842
112843     /**
112844      * Sets the scrollLeft and constrains the value between 0 and max.
112845      * @param {Number} scrollLeft
112846      * @return {Number} The resulting scrollLeft value after being constrained
112847      */
112848     setScrollLeft: function(scrollLeft) {
112849         var el = this.scrollEl,
112850             elDom = el && el.dom;
112851
112852         if (elDom) {
112853             return elDom.scrollLeft = Ext.Number.constrain(scrollLeft, 0, elDom.scrollWidth - elDom.clientWidth);
112854         }
112855     },
112856
112857     /**
112858      * Scroll by deltaY
112859      * @param {Number} delta
112860      * @return {Number} The resulting scrollTop value
112861      */
112862     scrollByDeltaY: function(delta) {
112863         var el = this.scrollEl,
112864             elDom = el && el.dom;
112865
112866         if (elDom) {
112867             return this.setScrollTop(elDom.scrollTop + delta);
112868         }
112869     },
112870
112871     /**
112872      * Scroll by deltaX
112873      * @param {Number} delta
112874      * @return {Number} The resulting scrollLeft value
112875      */
112876     scrollByDeltaX: function(delta) {
112877         var el = this.scrollEl,
112878             elDom = el && el.dom;
112879
112880         if (elDom) {
112881             return this.setScrollLeft(elDom.scrollLeft + delta);
112882         }
112883     },
112884
112885
112886     /**
112887      * Scroll to the top.
112888      */
112889     scrollToTop : function(){
112890         this.setScrollTop(0);
112891     },
112892
112893     // synchronize the scroller with the bound gridviews
112894     onElScroll: function(event, target) {
112895         this.fireEvent('bodyscroll', event, target);
112896     },
112897
112898     getPanel: function() {
112899         var me = this;
112900         if (!me.panel) {
112901             me.panel = this.up('[scrollerOwner]');
112902         }
112903         return me.panel;
112904     }
112905 });
112906
112907
112908 /**
112909  * @class Ext.grid.PagingScroller
112910  * @extends Ext.grid.Scroller
112911  */
112912 Ext.define('Ext.grid.PagingScroller', {
112913     extend: 'Ext.grid.Scroller',
112914     alias: 'widget.paginggridscroller',
112915     //renderTpl: null,
112916     //tpl: [
112917     //    '<tpl for="pages">',
112918     //        '<div class="' + Ext.baseCSSPrefix + 'stretcher" style="width: {width}px;height: {height}px;"></div>',
112919     //    '</tpl>'
112920     //],
112921     /**
112922      * @cfg {Number} percentageFromEdge This is a number above 0 and less than 1 which specifies
112923      * at what percentage to begin fetching the next page. For example if the pageSize is 100
112924      * and the percentageFromEdge is the default of 0.35, the paging scroller will prefetch pages
112925      * when scrolling up between records 0 and 34 and when scrolling down between records 65 and 99.
112926      */
112927     percentageFromEdge: 0.35,
112928
112929     /**
112930      * @cfg {Number} scrollToLoadBuffer This is the time in milliseconds to buffer load requests
112931      * when scrolling the PagingScrollbar.
112932      */
112933     scrollToLoadBuffer: 200,
112934
112935     activePrefetch: true,
112936
112937     chunkSize: 50,
112938     snapIncrement: 25,
112939
112940     syncScroll: true,
112941
112942     initComponent: function() {
112943         var me = this,
112944             ds = me.store;
112945
112946         ds.on('guaranteedrange', me.onGuaranteedRange, me);
112947         me.callParent(arguments);
112948     },
112949
112950     onGuaranteedRange: function(range, start, end) {
112951         var me = this,
112952             ds = me.store,
112953             rs;
112954         // this should never happen
112955         if (range.length && me.visibleStart < range[0].index) {
112956             return;
112957         }
112958
112959         ds.loadRecords(range);
112960
112961         if (!me.firstLoad) {
112962             if (me.rendered) {
112963                 me.invalidate();
112964             } else {
112965                 me.on('afterrender', me.invalidate, me, {single: true});
112966             }
112967             me.firstLoad = true;
112968         } else {
112969             // adjust to visible
112970             // only sync if there is a paging scrollbar element and it has a scroll height (meaning it's currently in the DOM)
112971             if (me.scrollEl && me.scrollEl.dom && me.scrollEl.dom.scrollHeight) {
112972                 me.syncTo();
112973             }
112974         }
112975     },
112976
112977     syncTo: function() {
112978         var me            = this,
112979             pnl           = me.getPanel(),
112980             store         = pnl.store,
112981             scrollerElDom = this.scrollEl.dom,
112982             rowOffset     = me.visibleStart - store.guaranteedStart,
112983             scrollBy      = rowOffset * me.rowHeight,
112984             scrollHeight  = scrollerElDom.scrollHeight,
112985             clientHeight  = scrollerElDom.clientHeight,
112986             scrollTop     = scrollerElDom.scrollTop,
112987             useMaximum;
112988             
112989
112990         // BrowserBug: clientHeight reports 0 in IE9 StrictMode
112991         // Instead we are using offsetHeight and hardcoding borders
112992         if (Ext.isIE9 && Ext.isStrict) {
112993             clientHeight = scrollerElDom.offsetHeight + 2;
112994         }
112995
112996         // This should always be zero or greater than zero but staying
112997         // safe and less than 0 we'll scroll to the bottom.
112998         useMaximum = (scrollHeight - clientHeight - scrollTop <= 0);
112999         this.setViewScrollTop(scrollBy, useMaximum);
113000     },
113001
113002     getPageData : function(){
113003         var panel = this.getPanel(),
113004             store = panel.store,
113005             totalCount = store.getTotalCount();
113006
113007         return {
113008             total : totalCount,
113009             currentPage : store.currentPage,
113010             pageCount: Math.ceil(totalCount / store.pageSize),
113011             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
113012             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
113013         };
113014     },
113015
113016     onElScroll: function(e, t) {
113017         var me = this,
113018             panel = me.getPanel(),
113019             store = panel.store,
113020             pageSize = store.pageSize,
113021             guaranteedStart = store.guaranteedStart,
113022             guaranteedEnd = store.guaranteedEnd,
113023             totalCount = store.getTotalCount(),
113024             numFromEdge = Math.ceil(me.percentageFromEdge * pageSize),
113025             position = t.scrollTop,
113026             visibleStart = Math.floor(position / me.rowHeight),
113027             view = panel.down('tableview'),
113028             viewEl = view.el,
113029             visibleHeight = viewEl.getHeight(),
113030             visibleAhead = Math.ceil(visibleHeight / me.rowHeight),
113031             visibleEnd = visibleStart + visibleAhead,
113032             prevPage = Math.floor(visibleStart / pageSize),
113033             nextPage = Math.floor(visibleEnd / pageSize) + 2,
113034             lastPage = Math.ceil(totalCount / pageSize),
113035             snap = me.snapIncrement,
113036             requestStart = Math.floor(visibleStart / snap) * snap,
113037             requestEnd = requestStart + pageSize - 1,
113038             activePrefetch = me.activePrefetch;
113039
113040         me.visibleStart = visibleStart;
113041         me.visibleEnd = visibleEnd;
113042         
113043         
113044         me.syncScroll = true;
113045         if (totalCount >= pageSize) {
113046             // end of request was past what the total is, grab from the end back a pageSize
113047             if (requestEnd > totalCount - 1) {
113048                 me.cancelLoad();
113049                 if (store.rangeSatisfied(totalCount - pageSize, totalCount - 1)) {
113050                     me.syncScroll = true;
113051                 }
113052                 store.guaranteeRange(totalCount - pageSize, totalCount - 1);
113053             // Out of range, need to reset the current data set
113054             } else if (visibleStart <= guaranteedStart || visibleEnd > guaranteedEnd) {
113055                 if (visibleStart <= guaranteedStart) {
113056                     // need to scroll up
113057                     requestStart -= snap;
113058                     requestEnd -= snap;
113059                     
113060                     if (requestStart < 0) {
113061                         requestStart = 0;
113062                         requestEnd = pageSize;
113063                     }
113064                 }
113065                 if (store.rangeSatisfied(requestStart, requestEnd)) {
113066                     me.cancelLoad();
113067                     store.guaranteeRange(requestStart, requestEnd);
113068                 } else {
113069                     store.mask();
113070                     me.attemptLoad(requestStart, requestEnd);
113071                 }
113072                 // dont sync the scroll view immediately, sync after the range has been guaranteed
113073                 me.syncScroll = false;
113074             } else if (activePrefetch && visibleStart < (guaranteedStart + numFromEdge) && prevPage > 0) {
113075                 me.syncScroll = true;
113076                 store.prefetchPage(prevPage);
113077             } else if (activePrefetch && visibleEnd > (guaranteedEnd - numFromEdge) && nextPage < lastPage) {
113078                 me.syncScroll = true;
113079                 store.prefetchPage(nextPage);
113080             }
113081         }
113082
113083         if (me.syncScroll) {
113084             me.syncTo();
113085         }
113086     },
113087
113088     getSizeCalculation: function() {
113089         // Use the direct ownerCt here rather than the scrollerOwner
113090         // because we are calculating widths/heights.
113091         var me     = this,
113092             owner  = me.ownerGrid,
113093             view   = owner.getView(),
113094             store  = me.store,
113095             dock   = me.dock,
113096             elDom  = me.el.dom,
113097             width  = 1,
113098             height = 1;
113099
113100         if (!me.rowHeight) {
113101             me.rowHeight = view.el.down(view.getItemSelector()).getHeight(false, true);
113102         }
113103
113104         // If the Store is *locally* filtered, use the filtered count from getCount.
113105         height = store[(!store.remoteFilter && store.isFiltered()) ? 'getCount' : 'getTotalCount']() * me.rowHeight;
113106
113107         if (isNaN(width)) {
113108             width = 1;
113109         }
113110         if (isNaN(height)) {
113111             height = 1;
113112         }
113113         return {
113114             width: width,
113115             height: height
113116         };
113117     },
113118
113119     attemptLoad: function(start, end) {
113120         var me = this;
113121         if (!me.loadTask) {
113122             me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
113123         }
113124         me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
113125     },
113126
113127     cancelLoad: function() {
113128         if (this.loadTask) {
113129             this.loadTask.cancel();
113130         }
113131     },
113132
113133     doAttemptLoad:  function(start, end) {
113134         var store = this.getPanel().store;
113135         store.guaranteeRange(start, end);
113136     },
113137
113138     setViewScrollTop: function(scrollTop, useMax) {
113139         var me = this,
113140             owner = me.getPanel(),
113141             items = owner.query('tableview'),
113142             i = 0,
113143             len = items.length,
113144             center,
113145             centerEl,
113146             calcScrollTop,
113147             maxScrollTop,
113148             scrollerElDom = me.el.dom;
113149
113150         owner.virtualScrollTop = scrollTop;
113151
113152         center = items[1] || items[0];
113153         centerEl = center.el.dom;
113154
113155         maxScrollTop = ((owner.store.pageSize * me.rowHeight) - centerEl.clientHeight);
113156         calcScrollTop = (scrollTop % ((owner.store.pageSize * me.rowHeight) + 1));
113157         if (useMax) {
113158             calcScrollTop = maxScrollTop;
113159         }
113160         if (calcScrollTop > maxScrollTop) {
113161             //Ext.Error.raise("Calculated scrollTop was larger than maxScrollTop");
113162             return;
113163             // calcScrollTop = maxScrollTop;
113164         }
113165         for (; i < len; i++) {
113166             items[i].el.dom.scrollTop = calcScrollTop;
113167         }
113168     }
113169 });
113170
113171 /**
113172  * @author Nicolas Ferrero
113173  *
113174  * TablePanel is the basis of both {@link Ext.tree.Panel TreePanel} and {@link Ext.grid.Panel GridPanel}.
113175  *
113176  * TablePanel aggregates:
113177  *
113178  *  - a Selection Model
113179  *  - a View
113180  *  - a Store
113181  *  - Scrollers
113182  *  - Ext.grid.header.Container
113183  */
113184 Ext.define('Ext.panel.Table', {
113185     extend: 'Ext.panel.Panel',
113186
113187     alias: 'widget.tablepanel',
113188
113189     uses: [
113190         'Ext.selection.RowModel',
113191         'Ext.grid.Scroller',
113192         'Ext.grid.header.Container',
113193         'Ext.grid.Lockable'
113194     ],
113195
113196     extraBaseCls: Ext.baseCSSPrefix + 'grid',
113197     extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
113198
113199     layout: 'fit',
113200     /**
113201      * @property {Boolean} hasView
113202      * True to indicate that a view has been injected into the panel.
113203      */
113204     hasView: false,
113205
113206     // each panel should dictate what viewType and selType to use
113207     /**
113208      * @cfg {String} viewType
113209      * An xtype of view to use. This is automatically set to 'gridview' by {@link Ext.grid.Panel Grid}
113210      * and to 'treeview' by {@link Ext.tree.Panel Tree}.
113211      */
113212     viewType: null,
113213
113214     /**
113215      * @cfg {Object} viewConfig
113216      * A config object that will be applied to the grid's UI view. Any of the config options available for
113217      * {@link Ext.view.Table} can be specified here. This option is ignored if {@link #view} is specified.
113218      */
113219
113220     /**
113221      * @cfg {Ext.view.Table} view
113222      * The {@link Ext.view.Table} used by the grid. Use {@link #viewConfig} to just supply some config options to
113223      * view (instead of creating an entire View instance).
113224      */
113225
113226     /**
113227      * @cfg {String} selType
113228      * An xtype of selection model to use. Defaults to 'rowmodel'. This is used to create selection model if just
113229      * a config object or nothing at all given in {@link #selModel} config.
113230      */
113231     selType: 'rowmodel',
113232
113233     /**
113234      * @cfg {Ext.selection.Model/Object} selModel
113235      * A {@link Ext.selection.Model selection model} instance or config object.  In latter case the {@link #selType}
113236      * config option determines to which type of selection model this config is applied.
113237      */
113238
113239     /**
113240      * @cfg {Boolean} multiSelect
113241      * True to enable 'MULTI' selection mode on selection model. See {@link Ext.selection.Model#mode}.
113242      */
113243
113244     /**
113245      * @cfg {Boolean} simpleSelect
113246      * True to enable 'SIMPLE' selection mode on selection model. See {@link Ext.selection.Model#mode}.
113247      */
113248
113249     /**
113250      * @cfg {Ext.data.Store} store (required)
113251      * The {@link Ext.data.Store Store} the grid should use as its data source.
113252      */
113253
113254     /**
113255      * @cfg {Number} scrollDelta
113256      * Number of pixels to scroll when scrolling with mousewheel.
113257      */
113258     scrollDelta: 40,
113259
113260     /**
113261      * @cfg {String/Boolean} scroll
113262      * Scrollers configuration. Valid values are 'both', 'horizontal' or 'vertical'.
113263      * True implies 'both'. False implies 'none'.
113264      */
113265     scroll: true,
113266
113267     /**
113268      * @cfg {Ext.grid.column.Column[]} columns
113269      * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this
113270      * grid. Each column definition provides the header text for the column, and a definition of where the data for that
113271      * column comes from.
113272      */
113273
113274     /**
113275      * @cfg {Boolean} forceFit
113276      * Ttrue to force the columns to fit into the available width. Headers are first sized according to configuration,
113277      * whether that be a specific width, or flex. Then they are all proportionally changed in width so that the entire
113278      * content width is used.
113279      */
113280
113281     /**
113282      * @cfg {Ext.grid.feature.Feature[]} features
113283      * An array of grid Features to be added to this grid. See {@link Ext.grid.feature.Feature} for usage.
113284      */
113285
113286     /**
113287      * @cfg {Boolean} [hideHeaders=false]
113288      * True to hide column headers.
113289      */
113290
113291     /**
113292      * @cfg {Boolean} deferRowRender
113293      * Defaults to true to enable deferred row rendering.
113294      *
113295      * This allows the View to execute a refresh quickly, with the expensive update of the row structure deferred so
113296      * that layouts with GridPanels appear, and lay out more quickly.
113297      */
113298
113299      deferRowRender: true,
113300      
113301     /**
113302      * @cfg {Boolean} sortableColumns
113303      * False to disable column sorting via clicking the header and via the Sorting menu items.
113304      */
113305     sortableColumns: true,
113306
113307     /**
113308      * @cfg {Boolean} [enableLocking=false]
113309      * True to enable locking support for this grid. Alternatively, locking will also be automatically
113310      * enabled if any of the columns in the column configuration contain the locked config option.
113311      */
113312     enableLocking: false,
113313
113314     verticalScrollDock: 'right',
113315     verticalScrollerType: 'gridscroller',
113316
113317     horizontalScrollerPresentCls: Ext.baseCSSPrefix + 'horizontal-scroller-present',
113318     verticalScrollerPresentCls: Ext.baseCSSPrefix + 'vertical-scroller-present',
113319
113320     // private property used to determine where to go down to find views
113321     // this is here to support locking.
113322     scrollerOwner: true,
113323
113324     invalidateScrollerOnRefresh: true,
113325
113326     /**
113327      * @cfg {Boolean} enableColumnMove
113328      * False to disable column dragging within this grid.
113329      */
113330     enableColumnMove: true,
113331
113332     /**
113333      * @cfg {Boolean} enableColumnResize
113334      * False to disable column resizing within this grid.
113335      */
113336     enableColumnResize: true,
113337
113338     /**
113339      * @cfg {Boolean} enableColumnHide
113340      * False to disable column hiding within this grid.
113341      */
113342     enableColumnHide: true,
113343
113344     initComponent: function() {
113345         if (!this.viewType) {
113346             Ext.Error.raise("You must specify a viewType config.");
113347         }
113348         if (this.headers) {
113349             Ext.Error.raise("The headers config is not supported. Please specify columns instead.");
113350         }
113351
113352         var me          = this,
113353             scroll      = me.scroll,
113354             vertical    = false,
113355             horizontal  = false,
113356             headerCtCfg = me.columns || me.colModel,
113357             i           = 0,
113358             view,
113359             border = me.border;
113360
113361         if (me.hideHeaders) {
113362             border = false;
113363         }
113364
113365         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
113366         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
113367
113368         // The columns/colModel config may be either a fully instantiated HeaderContainer, or an array of Column definitions, or a config object of a HeaderContainer
113369         // Either way, we extract a columns property referencing an array of Column definitions.
113370         if (headerCtCfg instanceof Ext.grid.header.Container) {
113371             me.headerCt = headerCtCfg;
113372             me.headerCt.border = border;
113373             me.columns = me.headerCt.items.items;
113374         } else {
113375             if (Ext.isArray(headerCtCfg)) {
113376                 headerCtCfg = {
113377                     items: headerCtCfg,
113378                     border: border
113379                 };
113380             }
113381             Ext.apply(headerCtCfg, {
113382                 forceFit: me.forceFit,
113383                 sortable: me.sortableColumns,
113384                 enableColumnMove: me.enableColumnMove,
113385                 enableColumnResize: me.enableColumnResize,
113386                 enableColumnHide: me.enableColumnHide,
113387                 border:  border
113388             });
113389             me.columns = headerCtCfg.items;
113390
113391              // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a
113392              // special view will be injected by the Ext.grid.Lockable mixin, so no processing of .
113393              if (me.enableLocking || Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) {
113394                  me.self.mixin('lockable', Ext.grid.Lockable);
113395                  me.injectLockable();
113396              }
113397         }
113398
113399         me.addEvents(
113400             /**
113401              * @event reconfigure
113402              * Fires after a reconfigure.
113403              * @param {Ext.panel.Table} this
113404              */
113405             'reconfigure',
113406             /**
113407              * @event viewready
113408              * Fires when the grid view is available (use this for selecting a default row).
113409              * @param {Ext.panel.Table} this
113410              */
113411             'viewready',
113412             /**
113413              * @event scrollerhide
113414              * Fires when a scroller is hidden.
113415              * @param {Ext.grid.Scroller} scroller
113416              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
113417              */
113418             'scrollerhide',
113419             /**
113420              * @event scrollershow
113421              * Fires when a scroller is shown.
113422              * @param {Ext.grid.Scroller} scroller
113423              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
113424              */
113425             'scrollershow'
113426         );
113427
113428         me.bodyCls = me.bodyCls || '';
113429         me.bodyCls += (' ' + me.extraBodyCls);
113430         
113431         me.cls = me.cls || '';
113432         me.cls += (' ' + me.extraBaseCls);
113433
113434         // autoScroll is not a valid configuration
113435         delete me.autoScroll;
113436
113437         // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property)
113438         // 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.
113439         if (!me.hasView) {
113440
113441             // If we were not configured with a ready-made headerCt (either by direct config with a headerCt property, or by passing
113442             // a HeaderContainer instance as the 'columns' property, then go ahead and create one from the config object created above.
113443             if (!me.headerCt) {
113444                 me.headerCt = Ext.create('Ext.grid.header.Container', headerCtCfg);
113445             }
113446
113447             // Extract the array of Column objects
113448             me.columns = me.headerCt.items.items;
113449
113450             if (me.hideHeaders) {
113451                 me.headerCt.height = 0;
113452                 me.headerCt.border = false;
113453                 me.headerCt.addCls(Ext.baseCSSPrefix + 'grid-header-ct-hidden');
113454                 me.addCls(Ext.baseCSSPrefix + 'grid-header-hidden');
113455                 // IE Quirks Mode fix
113456                 // If hidden configuration option was used, several layout calculations will be bypassed.
113457                 if (Ext.isIEQuirks) {
113458                     me.headerCt.style = {
113459                         display: 'none'
113460                     };
113461                 }
113462             }
113463
113464             // turn both on.
113465             if (scroll === true || scroll === 'both') {
113466                 vertical = horizontal = true;
113467             } else if (scroll === 'horizontal') {
113468                 horizontal = true;
113469             } else if (scroll === 'vertical') {
113470                 vertical = true;
113471             // All other values become 'none' or false.
113472             } else {
113473                 me.headerCt.availableSpaceOffset = 0;
113474             }
113475
113476             if (vertical) {
113477                 me.verticalScroller = Ext.ComponentManager.create(me.initVerticalScroller());
113478                 me.mon(me.verticalScroller, {
113479                     bodyscroll: me.onVerticalScroll,
113480                     scope: me
113481                 });
113482             }
113483
113484             if (horizontal) {
113485                 me.horizontalScroller = Ext.ComponentManager.create(me.initHorizontalScroller());
113486                 me.mon(me.horizontalScroller, {
113487                     bodyscroll: me.onHorizontalScroll,
113488                     scope: me
113489                 });
113490             }
113491
113492             me.headerCt.on('resize', me.onHeaderResize, me);
113493             me.relayHeaderCtEvents(me.headerCt);
113494             me.features = me.features || [];
113495             if (!Ext.isArray(me.features)) {
113496                 me.features = [me.features];
113497             }
113498             me.dockedItems = me.dockedItems || [];
113499             me.dockedItems.unshift(me.headerCt);
113500             me.viewConfig = me.viewConfig || {};
113501             me.viewConfig.invalidateScrollerOnRefresh = me.invalidateScrollerOnRefresh;
113502
113503             // AbstractDataView will look up a Store configured as an object
113504             // getView converts viewConfig into a View instance
113505             view = me.getView();
113506
113507             view.on({
113508                 afterrender: function () {
113509                     // hijack the view el's scroll method
113510                     view.el.scroll = Ext.Function.bind(me.elScroll, me);
113511                     // We use to listen to document.body wheel events, but that's a
113512                     // little much. We scope just to the view now.
113513                     me.mon(view.el, {
113514                         mousewheel: me.onMouseWheel,
113515                         scope: me
113516                     });
113517                 },
113518                 single: true
113519             });
113520             me.items = [view];
113521             me.hasView = true;
113522
113523             me.mon(view.store, {
113524                 load: me.onStoreLoad,
113525                 scope: me
113526             });
113527             me.mon(view, {
113528                 viewReady: me.onViewReady,
113529                 resize: me.onViewResize,
113530                 refresh: {
113531                     fn: me.onViewRefresh,
113532                     scope: me,
113533                     buffer: 50
113534                 },
113535                 scope: me
113536             });
113537             this.relayEvents(view, [
113538                 /**
113539                  * @event beforeitemmousedown
113540                  * @alias Ext.view.View#beforeitemmousedown
113541                  */
113542                 'beforeitemmousedown',
113543                 /**
113544                  * @event beforeitemmouseup
113545                  * @alias Ext.view.View#beforeitemmouseup
113546                  */
113547                 'beforeitemmouseup',
113548                 /**
113549                  * @event beforeitemmouseenter
113550                  * @alias Ext.view.View#beforeitemmouseenter
113551                  */
113552                 'beforeitemmouseenter',
113553                 /**
113554                  * @event beforeitemmouseleave
113555                  * @alias Ext.view.View#beforeitemmouseleave
113556                  */
113557                 'beforeitemmouseleave',
113558                 /**
113559                  * @event beforeitemclick
113560                  * @alias Ext.view.View#beforeitemclick
113561                  */
113562                 'beforeitemclick',
113563                 /**
113564                  * @event beforeitemdblclick
113565                  * @alias Ext.view.View#beforeitemdblclick
113566                  */
113567                 'beforeitemdblclick',
113568                 /**
113569                  * @event beforeitemcontextmenu
113570                  * @alias Ext.view.View#beforeitemcontextmenu
113571                  */
113572                 'beforeitemcontextmenu',
113573                 /**
113574                  * @event itemmousedown
113575                  * @alias Ext.view.View#itemmousedown
113576                  */
113577                 'itemmousedown',
113578                 /**
113579                  * @event itemmouseup
113580                  * @alias Ext.view.View#itemmouseup
113581                  */
113582                 'itemmouseup',
113583                 /**
113584                  * @event itemmouseenter
113585                  * @alias Ext.view.View#itemmouseenter
113586                  */
113587                 'itemmouseenter',
113588                 /**
113589                  * @event itemmouseleave
113590                  * @alias Ext.view.View#itemmouseleave
113591                  */
113592                 'itemmouseleave',
113593                 /**
113594                  * @event itemclick
113595                  * @alias Ext.view.View#itemclick
113596                  */
113597                 'itemclick',
113598                 /**
113599                  * @event itemdblclick
113600                  * @alias Ext.view.View#itemdblclick
113601                  */
113602                 'itemdblclick',
113603                 /**
113604                  * @event itemcontextmenu
113605                  * @alias Ext.view.View#itemcontextmenu
113606                  */
113607                 'itemcontextmenu',
113608                 /**
113609                  * @event beforecontainermousedown
113610                  * @alias Ext.view.View#beforecontainermousedown
113611                  */
113612                 'beforecontainermousedown',
113613                 /**
113614                  * @event beforecontainermouseup
113615                  * @alias Ext.view.View#beforecontainermouseup
113616                  */
113617                 'beforecontainermouseup',
113618                 /**
113619                  * @event beforecontainermouseover
113620                  * @alias Ext.view.View#beforecontainermouseover
113621                  */
113622                 'beforecontainermouseover',
113623                 /**
113624                  * @event beforecontainermouseout
113625                  * @alias Ext.view.View#beforecontainermouseout
113626                  */
113627                 'beforecontainermouseout',
113628                 /**
113629                  * @event beforecontainerclick
113630                  * @alias Ext.view.View#beforecontainerclick
113631                  */
113632                 'beforecontainerclick',
113633                 /**
113634                  * @event beforecontainerdblclick
113635                  * @alias Ext.view.View#beforecontainerdblclick
113636                  */
113637                 'beforecontainerdblclick',
113638                 /**
113639                  * @event beforecontainercontextmenu
113640                  * @alias Ext.view.View#beforecontainercontextmenu
113641                  */
113642                 'beforecontainercontextmenu',
113643                 /**
113644                  * @event containermouseup
113645                  * @alias Ext.view.View#containermouseup
113646                  */
113647                 'containermouseup',
113648                 /**
113649                  * @event containermouseover
113650                  * @alias Ext.view.View#containermouseover
113651                  */
113652                 'containermouseover',
113653                 /**
113654                  * @event containermouseout
113655                  * @alias Ext.view.View#containermouseout
113656                  */
113657                 'containermouseout',
113658                 /**
113659                  * @event containerclick
113660                  * @alias Ext.view.View#containerclick
113661                  */
113662                 'containerclick',
113663                 /**
113664                  * @event containerdblclick
113665                  * @alias Ext.view.View#containerdblclick
113666                  */
113667                 'containerdblclick',
113668                 /**
113669                  * @event containercontextmenu
113670                  * @alias Ext.view.View#containercontextmenu
113671                  */
113672                 'containercontextmenu',
113673                 /**
113674                  * @event selectionchange
113675                  * @alias Ext.selection.Model#selectionchange
113676                  */
113677                 'selectionchange',
113678                 /**
113679                  * @event beforeselect
113680                  * @alias Ext.selection.RowModel#beforeselect
113681                  */
113682                 'beforeselect',
113683                 /**
113684                  * @event select
113685                  * @alias Ext.selection.RowModel#select
113686                  */
113687                 'select',
113688                 /**
113689                  * @event beforedeselect
113690                  * @alias Ext.selection.RowModel#beforedeselect
113691                  */
113692                 'beforedeselect',
113693                 /**
113694                  * @event deselect
113695                  * @alias Ext.selection.RowModel#deselect
113696                  */
113697                 'deselect'
113698             ]);
113699         }
113700
113701         me.callParent(arguments);
113702     },
113703     
113704     onRender: function(){
113705         var vScroll = this.verticalScroller,
113706             hScroll = this.horizontalScroller;
113707
113708         if (vScroll) {
113709             vScroll.ensureDimension();
113710         }
113711         if (hScroll) {
113712             hScroll.ensureDimension();
113713         }
113714         this.callParent(arguments);    
113715     },
113716
113717     // state management
113718     initStateEvents: function(){
113719         var events = this.stateEvents;
113720         // push on stateEvents if they don't exist
113721         Ext.each(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange'], function(event){
113722             if (Ext.Array.indexOf(events, event)) {
113723                 events.push(event);
113724             }
113725         });
113726         this.callParent();
113727     },
113728
113729     /**
113730      * Returns the horizontal scroller config.
113731      */
113732     initHorizontalScroller: function () {
113733         var me = this,
113734             ret = {
113735                 xtype: 'gridscroller',
113736                 dock: 'bottom',
113737                 section: me,
113738                 store: me.store
113739             };
113740
113741         return ret;
113742     },
113743
113744     /**
113745      * Returns the vertical scroller config.
113746      */
113747     initVerticalScroller: function () {
113748         var me = this,
113749             ret = me.verticalScroller || {};
113750
113751         Ext.applyIf(ret, {
113752             xtype: me.verticalScrollerType,
113753             dock: me.verticalScrollDock,
113754             store: me.store
113755         });
113756
113757         return ret;
113758     },
113759
113760     relayHeaderCtEvents: function (headerCt) {
113761         this.relayEvents(headerCt, [
113762             /**
113763              * @event columnresize
113764              * @alias Ext.grid.header.Container#columnresize
113765              */
113766             'columnresize',
113767             /**
113768              * @event columnmove
113769              * @alias Ext.grid.header.Container#columnmove
113770              */
113771             'columnmove',
113772             /**
113773              * @event columnhide
113774              * @alias Ext.grid.header.Container#columnhide
113775              */
113776             'columnhide',
113777             /**
113778              * @event columnshow
113779              * @alias Ext.grid.header.Container#columnshow
113780              */
113781             'columnshow',
113782             /**
113783              * @event sortchange
113784              * @alias Ext.grid.header.Container#sortchange
113785              */
113786             'sortchange'
113787         ]);
113788     },
113789
113790     getState: function(){
113791         var me = this,
113792             state = me.callParent(),
113793             sorter = me.store.sorters.first();
113794
113795         state.columns = (me.headerCt || me).getColumnsState();
113796
113797         if (sorter) {
113798             state.sort = {
113799                 property: sorter.property,
113800                 direction: sorter.direction
113801             };
113802         }
113803
113804         return state;
113805     },
113806
113807     applyState: function(state) {
113808         var me = this,
113809             sorter = state.sort,
113810             store = me.store,
113811             columns = state.columns;
113812
113813         delete state.columns;
113814
113815         // Ensure superclass has applied *its* state.
113816         // AbstractComponent saves dimensions (and anchor/flex) plus collapsed state.
113817         me.callParent(arguments);
113818
113819         if (columns) {
113820             (me.headerCt || me).applyColumnsState(columns);
113821         }
113822
113823         if (sorter) {
113824             if (store.remoteSort) {
113825                 store.sorters.add(Ext.create('Ext.util.Sorter', {
113826                     property: sorter.property,
113827                     direction: sorter.direction
113828                 }));
113829             }
113830             else {
113831                 store.sort(sorter.property, sorter.direction);
113832             }
113833         }
113834     },
113835
113836     /**
113837      * Returns the store associated with this Panel.
113838      * @return {Ext.data.Store} The store
113839      */
113840     getStore: function(){
113841         return this.store;
113842     },
113843
113844     /**
113845      * Gets the view for this panel.
113846      * @return {Ext.view.Table}
113847      */
113848     getView: function() {
113849         var me = this,
113850             sm;
113851
113852         if (!me.view) {
113853             sm = me.getSelectionModel();
113854             me.view = me.createComponent(Ext.apply({}, me.viewConfig, {
113855                 deferInitialRefresh: me.deferRowRender,
113856                 xtype: me.viewType,
113857                 store: me.store,
113858                 headerCt: me.headerCt,
113859                 selModel: sm,
113860                 features: me.features,
113861                 panel: me
113862             }));
113863             me.mon(me.view, {
113864                 uievent: me.processEvent,
113865                 scope: me
113866             });
113867             sm.view = me.view;
113868             me.headerCt.view = me.view;
113869             me.relayEvents(me.view, ['cellclick', 'celldblclick']);
113870         }
113871         return me.view;
113872     },
113873
113874     /**
113875      * @private
113876      * @override
113877      * autoScroll is never valid for all classes which extend TablePanel.
113878      */
113879     setAutoScroll: Ext.emptyFn,
113880
113881     // This method hijacks Ext.view.Table's el scroll method.
113882     // This enables us to keep the virtualized scrollbars in sync
113883     // with the view. It currently does NOT support animation.
113884     elScroll: function(direction, distance, animate) {
113885         var me = this,
113886             scroller;
113887
113888         if (direction === "up" || direction === "left") {
113889             distance = -distance;
113890         }
113891         
113892         if (direction === "down" || direction === "up") {
113893             scroller = me.getVerticalScroller();
113894             
113895             //if the grid does not currently need a vertical scroller don't try to update it (EXTJSIV-3891)
113896             if (scroller) {
113897                 scroller.scrollByDeltaY(distance);
113898             }
113899         } else {
113900             scroller = me.getHorizontalScroller();
113901             
113902             //if the grid does not currently need a horizontal scroller don't try to update it (EXTJSIV-3891)
113903             if (scroller) {
113904                 scroller.scrollByDeltaX(distance);
113905             }
113906         }
113907     },
113908
113909     /**
113910      * @private
113911      * Processes UI events from the view. Propagates them to whatever internal Components need to process them.
113912      * @param {String} type Event type, eg 'click'
113913      * @param {Ext.view.Table} view TableView Component
113914      * @param {HTMLElement} cell Cell HtmlElement the event took place within
113915      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
113916      * @param {Number} cellIndex Cell index within the row
113917      * @param {Ext.EventObject} e Original event
113918      */
113919     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
113920         var me = this,
113921             header;
113922
113923         if (cellIndex !== -1) {
113924             header = me.headerCt.getGridColumns()[cellIndex];
113925             return header.processEvent.apply(header, arguments);
113926         }
113927     },
113928
113929     /**
113930      * Requests a recalculation of scrollbars and puts them in if they are needed.
113931      */
113932     determineScrollbars: function() {
113933         // Set a flag so that afterComponentLayout does not recurse back into here.
113934         if (this.determineScrollbarsRunning) {
113935             return;
113936         }
113937         this.determineScrollbarsRunning = true;
113938         var me = this,
113939             view = me.view,
113940             box,
113941             tableEl,
113942             scrollWidth,
113943             clientWidth,
113944             scrollHeight,
113945             clientHeight,
113946             verticalScroller = me.verticalScroller,
113947             horizontalScroller = me.horizontalScroller,
113948             curScrollbars = (verticalScroller   && verticalScroller.ownerCt === me ? 1 : 0) |
113949                             (horizontalScroller && horizontalScroller.ownerCt === me ? 2 : 0),
113950             reqScrollbars = 0; // 1 = vertical, 2 = horizontal, 3 = both
113951
113952         // If we are not collapsed, and the view has been rendered AND filled, then we can determine scrollbars
113953         if (!me.collapsed && view && view.viewReady) {
113954
113955             // Calculate maximum, *scrollbarless* space which the view has available.
113956             // It will be the Fit Layout's calculated size, plus the widths of any currently shown scrollbars
113957             box = view.el.getSize();
113958
113959             clientWidth  = box.width  + ((curScrollbars & 1) ? verticalScroller.width : 0);
113960             clientHeight = box.height + ((curScrollbars & 2) ? horizontalScroller.height : 0);
113961
113962             // Calculate the width of the scrolling block
113963             // There will never be a horizontal scrollbar if all columns are flexed.
113964
113965             scrollWidth = (me.headerCt.query('[flex]').length && !me.headerCt.layout.tooNarrow) ? 0 : me.headerCt.getFullWidth();
113966
113967             // Calculate the height of the scrolling block
113968             if (verticalScroller && verticalScroller.el) {
113969                 scrollHeight = verticalScroller.getSizeCalculation().height;
113970             } else {
113971                 tableEl = view.el.child('table', true);
113972                 scrollHeight = tableEl ? tableEl.offsetHeight : 0;
113973             }
113974
113975             // View is too high.
113976             // Definitely need a vertical scrollbar
113977             if (scrollHeight > clientHeight) {
113978                 reqScrollbars = 1;
113979
113980                 // But if scrollable block width goes into the zone required by the vertical scrollbar, we'll also need a horizontal
113981                 if (horizontalScroller && ((clientWidth - scrollWidth) < verticalScroller.width)) {
113982                     reqScrollbars = 3;
113983                 }
113984             }
113985
113986             // View height fits. But we stil may need a horizontal scrollbar, and this might necessitate a vertical one.
113987             else {
113988                 // View is too wide.
113989                 // Definitely need a horizontal scrollbar
113990                 if (scrollWidth > clientWidth) {
113991                     reqScrollbars = 2;
113992
113993                     // But if scrollable block height goes into the zone required by the horizontal scrollbar, we'll also need a vertical
113994                     if (verticalScroller && ((clientHeight - scrollHeight) < horizontalScroller.height)) {
113995                         reqScrollbars = 3;
113996                     }
113997                 }
113998             }
113999
114000             // If scrollbar requirements have changed, change 'em...
114001             if (reqScrollbars !== curScrollbars) {
114002
114003                 // Suspend component layout while we add/remove the docked scrollers
114004                 me.suspendLayout = true;
114005                 if (reqScrollbars & 1) {
114006                     me.showVerticalScroller();
114007                 } else {
114008                     me.hideVerticalScroller();
114009                 }
114010                 if (reqScrollbars & 2) {
114011                     me.showHorizontalScroller();
114012                 } else {
114013                     me.hideHorizontalScroller();
114014                 }
114015                 me.suspendLayout = false;
114016
114017                 // Lay out the Component.
114018                 me.doComponentLayout();
114019                 // Lay out me.items
114020                 me.getLayout().layout();
114021             }
114022         }
114023         delete me.determineScrollbarsRunning;
114024     },
114025
114026     onViewResize: function() {
114027         this.determineScrollbars();
114028     },
114029
114030     afterComponentLayout: function() {
114031         this.callParent(arguments);
114032         this.determineScrollbars();
114033         this.invalidateScroller();
114034     },
114035
114036     onHeaderResize: function() {
114037         if (!this.componentLayout.layoutBusy && this.view && this.view.rendered) {
114038             this.determineScrollbars();
114039             this.invalidateScroller();
114040         }
114041     },
114042
114043     afterCollapse: function() {
114044         var me = this;
114045         if (me.verticalScroller) {
114046             me.verticalScroller.saveScrollPos();
114047         }
114048         if (me.horizontalScroller) {
114049             me.horizontalScroller.saveScrollPos();
114050         }
114051         me.callParent(arguments);
114052     },
114053
114054     afterExpand: function() {
114055         var me = this;
114056         me.callParent(arguments);
114057         if (me.verticalScroller) {
114058             me.verticalScroller.restoreScrollPos();
114059         }
114060         if (me.horizontalScroller) {
114061             me.horizontalScroller.restoreScrollPos();
114062         }
114063     },
114064
114065     /**
114066      * Hides the verticalScroller and removes the horizontalScrollerPresentCls.
114067      */
114068     hideHorizontalScroller: function() {
114069         var me = this;
114070
114071         if (me.horizontalScroller && me.horizontalScroller.ownerCt === me) {
114072             me.verticalScroller.setReservedSpace(0);
114073             me.removeDocked(me.horizontalScroller, false);
114074             me.removeCls(me.horizontalScrollerPresentCls);
114075             me.fireEvent('scrollerhide', me.horizontalScroller, 'horizontal');
114076         }
114077
114078     },
114079
114080     /**
114081      * Shows the horizontalScroller and add the horizontalScrollerPresentCls.
114082      */
114083     showHorizontalScroller: function() {
114084         var me = this;
114085
114086         if (me.verticalScroller) {
114087             me.verticalScroller.setReservedSpace(Ext.getScrollbarSize().height - 1);
114088         }
114089         if (me.horizontalScroller && me.horizontalScroller.ownerCt !== me) {
114090             me.addDocked(me.horizontalScroller);
114091             me.addCls(me.horizontalScrollerPresentCls);
114092             me.fireEvent('scrollershow', me.horizontalScroller, 'horizontal');
114093         }
114094     },
114095
114096     /**
114097      * Hides the verticalScroller and removes the verticalScrollerPresentCls.
114098      */
114099     hideVerticalScroller: function() {
114100         var me = this;
114101
114102         me.setHeaderReserveOffset(false);
114103         if (me.verticalScroller && me.verticalScroller.ownerCt === me) {
114104             me.removeDocked(me.verticalScroller, false);
114105             me.removeCls(me.verticalScrollerPresentCls);
114106             me.fireEvent('scrollerhide', me.verticalScroller, 'vertical');
114107         }
114108     },
114109
114110     /**
114111      * Shows the verticalScroller and adds the verticalScrollerPresentCls.
114112      */
114113     showVerticalScroller: function() {
114114         var me = this;
114115
114116         me.setHeaderReserveOffset(true);
114117         if (me.verticalScroller && me.verticalScroller.ownerCt !== me) {
114118             me.addDocked(me.verticalScroller);
114119             me.addCls(me.verticalScrollerPresentCls);
114120             me.fireEvent('scrollershow', me.verticalScroller, 'vertical');
114121         }
114122     },
114123
114124     setHeaderReserveOffset: function (reserveOffset) {
114125         var headerCt = this.headerCt,
114126             layout = headerCt.layout;
114127
114128         // only trigger a layout when reserveOffset is changing
114129         if (layout && layout.reserveOffset !== reserveOffset) {
114130             layout.reserveOffset = reserveOffset;
114131             if (!this.suspendLayout) {
114132                 headerCt.doLayout();
114133             }
114134         }
114135     },
114136
114137     /**
114138      * Invalides scrollers that are present and forces a recalculation. (Not related to showing/hiding the scrollers)
114139      */
114140     invalidateScroller: function() {
114141         var me = this,
114142             vScroll = me.verticalScroller,
114143             hScroll = me.horizontalScroller;
114144
114145         if (vScroll) {
114146             vScroll.invalidate();
114147         }
114148         if (hScroll) {
114149             hScroll.invalidate();
114150         }
114151     },
114152
114153     // refresh the view when a header moves
114154     onHeaderMove: function(headerCt, header, fromIdx, toIdx) {
114155         this.view.refresh();
114156     },
114157
114158     // Section onHeaderHide is invoked after view.
114159     onHeaderHide: function(headerCt, header) {
114160         this.invalidateScroller();
114161     },
114162
114163     onHeaderShow: function(headerCt, header) {
114164         this.invalidateScroller();
114165     },
114166
114167     getVerticalScroller: function() {
114168         return this.getScrollerOwner().down('gridscroller[dock=' + this.verticalScrollDock + ']');
114169     },
114170
114171     getHorizontalScroller: function() {
114172         return this.getScrollerOwner().down('gridscroller[dock=bottom]');
114173     },
114174
114175     onMouseWheel: function(e) {
114176         var me = this,
114177             vertScroller = me.getVerticalScroller(),
114178             horizScroller = me.getHorizontalScroller(),
114179             scrollDelta = -me.scrollDelta,
114180             deltas = e.getWheelDeltas(),
114181             deltaX = scrollDelta * deltas.x,
114182             deltaY = scrollDelta * deltas.y,
114183             vertScrollerEl, horizScrollerEl,
114184             vertScrollerElDom, horizScrollerElDom,
114185             horizontalCanScrollLeft, horizontalCanScrollRight,
114186             verticalCanScrollDown, verticalCanScrollUp;
114187
114188         // calculate whether or not both scrollbars can scroll right/left and up/down
114189         if (horizScroller) {
114190             horizScrollerEl = horizScroller.scrollEl;
114191             if (horizScrollerEl) {
114192                 horizScrollerElDom = horizScrollerEl.dom;
114193                 horizontalCanScrollRight = horizScrollerElDom.scrollLeft !== horizScrollerElDom.scrollWidth - horizScrollerElDom.clientWidth;
114194                 horizontalCanScrollLeft  = horizScrollerElDom.scrollLeft !== 0;
114195             }
114196         }
114197         if (vertScroller) {
114198             vertScrollerEl = vertScroller.scrollEl;
114199             if (vertScrollerEl) {
114200                 vertScrollerElDom = vertScrollerEl.dom;
114201                 verticalCanScrollDown = vertScrollerElDom.scrollTop !== vertScrollerElDom.scrollHeight - vertScrollerElDom.clientHeight;
114202                 verticalCanScrollUp   = vertScrollerElDom.scrollTop !== 0;
114203             }
114204         }
114205
114206         if (horizScroller) {
114207             if ((deltaX < 0 && horizontalCanScrollLeft) || (deltaX > 0 && horizontalCanScrollRight)) {
114208                 e.stopEvent();
114209                 horizScroller.scrollByDeltaX(deltaX);
114210             }
114211         }
114212         if (vertScroller) {
114213             if ((deltaY < 0 && verticalCanScrollUp) || (deltaY > 0 && verticalCanScrollDown)) {
114214                 e.stopEvent();
114215                 vertScroller.scrollByDeltaY(deltaY);
114216             }
114217         }
114218     },
114219
114220     /**
114221      * @private
114222      * Fires the TablePanel's viewready event when the view declares that its internal DOM is ready
114223      */
114224     onViewReady: function() {
114225         var me = this;
114226         me.fireEvent('viewready', me);
114227         if (me.deferRowRender) {
114228             me.determineScrollbars();
114229             me.invalidateScroller();
114230         }
114231     },
114232
114233     /**
114234      * @private
114235      * Determines and invalidates scrollers on view refresh
114236      */
114237     onViewRefresh: function() {
114238         var me = this;
114239
114240         // Refresh *during* render must be ignored.
114241         if (!me.rendering) {
114242             this.determineScrollbars();
114243             if (this.invalidateScrollerOnRefresh) {
114244                 this.invalidateScroller();
114245             }
114246         }
114247     },
114248
114249     /**
114250      * Sets the scrollTop of the TablePanel.
114251      * @param {Number} top
114252      */
114253     setScrollTop: function(top) {
114254         var me               = this,
114255             rootCmp          = me.getScrollerOwner(),
114256             verticalScroller = me.getVerticalScroller();
114257
114258         rootCmp.virtualScrollTop = top;
114259         if (verticalScroller) {
114260             verticalScroller.setScrollTop(top);
114261         }
114262     },
114263
114264     getScrollerOwner: function() {
114265         var rootCmp = this;
114266         if (!this.scrollerOwner) {
114267             rootCmp = this.up('[scrollerOwner]');
114268         }
114269         return rootCmp;
114270     },
114271
114272     /**
114273      * Scrolls the TablePanel by deltaY
114274      * @param {Number} deltaY
114275      */
114276     scrollByDeltaY: function(deltaY) {
114277         var verticalScroller = this.getVerticalScroller();
114278
114279         if (verticalScroller) {
114280             verticalScroller.scrollByDeltaY(deltaY);
114281         }
114282     },
114283
114284     /**
114285      * Scrolls the TablePanel by deltaX
114286      * @param {Number} deltaX
114287      */
114288     scrollByDeltaX: function(deltaX) {
114289         var horizontalScroller = this.getHorizontalScroller();
114290
114291         if (horizontalScroller) {
114292             horizontalScroller.scrollByDeltaX(deltaX);
114293         }
114294     },
114295
114296     /**
114297      * Gets left hand side marker for header resizing.
114298      * @private
114299      */
114300     getLhsMarker: function() {
114301         var me = this;
114302
114303         if (!me.lhsMarker) {
114304             me.lhsMarker = Ext.DomHelper.append(me.el, {
114305                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
114306             }, true);
114307         }
114308         return me.lhsMarker;
114309     },
114310
114311     /**
114312      * Gets right hand side marker for header resizing.
114313      * @private
114314      */
114315     getRhsMarker: function() {
114316         var me = this;
114317
114318         if (!me.rhsMarker) {
114319             me.rhsMarker = Ext.DomHelper.append(me.el, {
114320                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
114321             }, true);
114322         }
114323         return me.rhsMarker;
114324     },
114325
114326     /**
114327      * Returns the selection model being used and creates it via the configuration if it has not been created already.
114328      * @return {Ext.selection.Model} selModel
114329      */
114330     getSelectionModel: function(){
114331         if (!this.selModel) {
114332             this.selModel = {};
114333         }
114334
114335         var mode = 'SINGLE',
114336             type;
114337         if (this.simpleSelect) {
114338             mode = 'SIMPLE';
114339         } else if (this.multiSelect) {
114340             mode = 'MULTI';
114341         }
114342
114343         Ext.applyIf(this.selModel, {
114344             allowDeselect: this.allowDeselect,
114345             mode: mode
114346         });
114347
114348         if (!this.selModel.events) {
114349             type = this.selModel.selType || this.selType;
114350             this.selModel = Ext.create('selection.' + type, this.selModel);
114351         }
114352
114353         if (!this.selModel.hasRelaySetup) {
114354             this.relayEvents(this.selModel, [
114355                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
114356             ]);
114357             this.selModel.hasRelaySetup = true;
114358         }
114359
114360         // lock the selection model if user
114361         // has disabled selection
114362         if (this.disableSelection) {
114363             this.selModel.locked = true;
114364         }
114365         return this.selModel;
114366     },
114367
114368     onVerticalScroll: function(event, target) {
114369         var owner = this.getScrollerOwner(),
114370             items = owner.query('tableview'),
114371             i = 0,
114372             len = items.length;
114373
114374         for (; i < len; i++) {
114375             items[i].el.dom.scrollTop = target.scrollTop;
114376         }
114377     },
114378
114379     onHorizontalScroll: function(event, target) {
114380         var owner = this.getScrollerOwner(),
114381             items = owner.query('tableview'),
114382             center = items[1] || items[0];
114383
114384         center.el.dom.scrollLeft = target.scrollLeft;
114385         this.headerCt.el.dom.scrollLeft = target.scrollLeft;
114386     },
114387
114388     // template method meant to be overriden
114389     onStoreLoad: Ext.emptyFn,
114390
114391     getEditorParent: function() {
114392         return this.body;
114393     },
114394
114395     bindStore: function(store) {
114396         var me = this;
114397         me.store = store;
114398         me.getView().bindStore(store);
114399     },
114400     
114401     beforeDestroy: function(){
114402         // may be some duplication here since the horizontal and vertical
114403         // scroller may be part of the docked items, but we need to clean
114404         // them up in case they aren't visible.
114405         Ext.destroy(this.horizontalScroller, this.verticalScroller);
114406         this.callParent();
114407     },
114408
114409     /**
114410      * Reconfigures the table with a new store/columns. Either the store or the columns can be ommitted if you don't wish
114411      * to change them.
114412      * @param {Ext.data.Store} store (Optional) The new store.
114413      * @param {Object[]} columns (Optional) An array of column configs
114414      */
114415     reconfigure: function(store, columns) {
114416         var me = this,
114417             headerCt = me.headerCt;
114418
114419         if (me.lockable) {
114420             me.reconfigureLockable(store, columns);
114421         } else {
114422             if (columns) {
114423                 headerCt.suspendLayout = true;
114424                 headerCt.removeAll();
114425                 headerCt.add(columns);
114426             }
114427             if (store) {
114428                 store = Ext.StoreManager.lookup(store);
114429                 me.bindStore(store);
114430             } else {
114431                 me.getView().refresh();
114432             }
114433             if (columns) {
114434                 headerCt.suspendLayout = false;
114435                 me.forceComponentLayout();
114436             }
114437         }
114438         me.fireEvent('reconfigure', me);
114439     }
114440 });
114441 /**
114442  * This class encapsulates the user interface for a tabular data set.
114443  * It acts as a centralized manager for controlling the various interface
114444  * elements of the view. This includes handling events, such as row and cell
114445  * level based DOM events. It also reacts to events from the underlying {@link Ext.selection.Model}
114446  * to provide visual feedback to the user.
114447  *
114448  * This class does not provide ways to manipulate the underlying data of the configured
114449  * {@link Ext.data.Store}.
114450  *
114451  * This is the base class for both {@link Ext.grid.View} and {@link Ext.tree.View} and is not
114452  * to be used directly.
114453  */
114454 Ext.define('Ext.view.Table', {
114455     extend: 'Ext.view.View',
114456     alias: 'widget.tableview',
114457     uses: [
114458         'Ext.view.TableChunker',
114459         'Ext.util.DelayedTask',
114460         'Ext.util.MixedCollection'
114461     ],
114462
114463     baseCls: Ext.baseCSSPrefix + 'grid-view',
114464
114465     // row
114466     itemSelector: '.' + Ext.baseCSSPrefix + 'grid-row',
114467     // cell
114468     cellSelector: '.' + Ext.baseCSSPrefix + 'grid-cell',
114469
114470     selectedItemCls: Ext.baseCSSPrefix + 'grid-row-selected',
114471     selectedCellCls: Ext.baseCSSPrefix + 'grid-cell-selected',
114472     focusedItemCls: Ext.baseCSSPrefix + 'grid-row-focused',
114473     overItemCls: Ext.baseCSSPrefix + 'grid-row-over',
114474     altRowCls:   Ext.baseCSSPrefix + 'grid-row-alt',
114475     rowClsRe: /(?:^|\s*)grid-row-(first|last|alt)(?:\s+|$)/g,
114476     cellRe: new RegExp('x-grid-cell-([^\\s]+) ', ''),
114477
114478     // cfg docs inherited
114479     trackOver: true,
114480
114481     /**
114482      * Override this function to apply custom CSS classes to rows during rendering. This function should return the
114483      * CSS class name (or empty string '' for none) that will be added to the row's wrapping div. To apply multiple
114484      * class names, simply return them space-delimited within the string (e.g. 'my-class another-class').
114485      * Example usage:
114486      *
114487      *     viewConfig: {
114488      *         getRowClass: function(record, rowIndex, rowParams, store){
114489      *             return record.get("valid") ? "row-valid" : "row-error";
114490      *         }
114491      *     }
114492      *
114493      * @param {Ext.data.Model} record The record corresponding to the current row.
114494      * @param {Number} index The row index.
114495      * @param {Object} rowParams **DEPRECATED.** For row body use the
114496      * {@link Ext.grid.feature.RowBody#getAdditionalData getAdditionalData} method of the rowbody feature.
114497      * @param {Ext.data.Store} store The store this grid is bound to
114498      * @return {String} a CSS class name to add to the row.
114499      * @method
114500      */
114501     getRowClass: null,
114502
114503     initComponent: function() {
114504         var me = this;
114505
114506         me.scrollState = {};
114507         me.selModel.view = me;
114508         me.headerCt.view = me;
114509         me.initFeatures();
114510         me.tpl = '<div></div>';
114511         me.callParent();
114512         me.mon(me.store, {
114513             load: me.onStoreLoad,
114514             scope: me
114515         });
114516
114517         // this.addEvents(
114518         //     /**
114519         //      * @event rowfocus
114520         //      * @param {Ext.data.Model} record
114521         //      * @param {HTMLElement} row
114522         //      * @param {Number} rowIdx
114523         //      */
114524         //     'rowfocus'
114525         // );
114526     },
114527
114528     // scroll to top of the grid when store loads
114529     onStoreLoad: function(){
114530         var me = this;
114531
114532         if (me.invalidateScrollerOnRefresh) {
114533             if (Ext.isGecko) {
114534                 if (!me.scrollToTopTask) {
114535                     me.scrollToTopTask = Ext.create('Ext.util.DelayedTask', me.scrollToTop, me);
114536                 }
114537                 me.scrollToTopTask.delay(1);
114538             } else {
114539                 me    .scrollToTop();
114540             }
114541         }
114542     },
114543
114544     // scroll the view to the top
114545     scrollToTop: Ext.emptyFn,
114546
114547     /**
114548      * Add a listener to the main view element. It will be destroyed with the view.
114549      * @private
114550      */
114551     addElListener: function(eventName, fn, scope){
114552         this.mon(this, eventName, fn, scope, {
114553             element: 'el'
114554         });
114555     },
114556
114557     /**
114558      * Get the columns used for generating a template via TableChunker.
114559      * See {@link Ext.grid.header.Container#getGridColumns}.
114560      * @private
114561      */
114562     getGridColumns: function() {
114563         return this.headerCt.getGridColumns();
114564     },
114565
114566     /**
114567      * Get a leaf level header by index regardless of what the nesting
114568      * structure is.
114569      * @private
114570      * @param {Number} index The index
114571      */
114572     getHeaderAtIndex: function(index) {
114573         return this.headerCt.getHeaderAtIndex(index);
114574     },
114575
114576     /**
114577      * Get the cell (td) for a particular record and column.
114578      * @param {Ext.data.Model} record
114579      * @param {Ext.grid.column.Column} column
114580      * @private
114581      */
114582     getCell: function(record, column) {
114583         var row = this.getNode(record);
114584         return Ext.fly(row).down(column.getCellSelector());
114585     },
114586
114587     /**
114588      * Get a reference to a feature
114589      * @param {String} id The id of the feature
114590      * @return {Ext.grid.feature.Feature} The feature. Undefined if not found
114591      */
114592     getFeature: function(id) {
114593         var features = this.featuresMC;
114594         if (features) {
114595             return features.get(id);
114596         }
114597     },
114598
114599     /**
114600      * Initializes each feature and bind it to this view.
114601      * @private
114602      */
114603     initFeatures: function() {
114604         var me = this,
114605             i = 0,
114606             features,
114607             len;
114608
114609         me.features = me.features || [];
114610         features = me.features;
114611         len = features.length;
114612
114613         me.featuresMC = Ext.create('Ext.util.MixedCollection');
114614         for (; i < len; i++) {
114615             // ensure feature hasnt already been instantiated
114616             if (!features[i].isFeature) {
114617                 features[i] = Ext.create('feature.' + features[i].ftype, features[i]);
114618             }
114619             // inject a reference to view
114620             features[i].view = me;
114621             me.featuresMC.add(features[i]);
114622         }
114623     },
114624
114625     /**
114626      * Gives features an injection point to attach events to the markup that
114627      * has been created for this view.
114628      * @private
114629      */
114630     attachEventsForFeatures: function() {
114631         var features = this.features,
114632             ln       = features.length,
114633             i        = 0;
114634
114635         for (; i < ln; i++) {
114636             if (features[i].isFeature) {
114637                 features[i].attachEvents();
114638             }
114639         }
114640     },
114641
114642     afterRender: function() {
114643         var me = this;
114644
114645         me.callParent();
114646         me.mon(me.el, {
114647             scroll: me.fireBodyScroll,
114648             scope: me
114649         });
114650         me.el.unselectable();
114651         me.attachEventsForFeatures();
114652     },
114653
114654     fireBodyScroll: function(e, t) {
114655         this.fireEvent('bodyscroll', e, t);
114656     },
114657
114658     // TODO: Refactor headerCt dependency here to colModel
114659     /**
114660      * Uses the headerCt to transform data from dataIndex keys in a record to
114661      * headerId keys in each header and then run them through each feature to
114662      * get additional data for variables they have injected into the view template.
114663      * @private
114664      */
114665     prepareData: function(data, idx, record) {
114666         var me       = this,
114667             orig     = me.headerCt.prepareData(data, idx, record, me, me.ownerCt),
114668             features = me.features,
114669             ln       = features.length,
114670             i        = 0,
114671             node, feature;
114672
114673         for (; i < ln; i++) {
114674             feature = features[i];
114675             if (feature.isFeature) {
114676                 Ext.apply(orig, feature.getAdditionalData(data, idx, record, orig, me));
114677             }
114678         }
114679
114680         return orig;
114681     },
114682
114683     // TODO: Refactor headerCt dependency here to colModel
114684     collectData: function(records, startIndex) {
114685         var preppedRecords = this.callParent(arguments),
114686             headerCt  = this.headerCt,
114687             fullWidth = headerCt.getFullWidth(),
114688             features  = this.features,
114689             ln = features.length,
114690             o = {
114691                 rows: preppedRecords,
114692                 fullWidth: fullWidth
114693             },
114694             i  = 0,
114695             feature,
114696             j = 0,
114697             jln,
114698             rowParams;
114699
114700         jln = preppedRecords.length;
114701         // process row classes, rowParams has been deprecated and has been moved
114702         // to the individual features that implement the behavior.
114703         if (this.getRowClass) {
114704             for (; j < jln; j++) {
114705                 rowParams = {};
114706                 preppedRecords[j]['rowCls'] = this.getRowClass(records[j], j, rowParams, this.store);
114707                 if (rowParams.alt) {
114708                     Ext.Error.raise("The getRowClass alt property is no longer supported.");
114709                 }
114710                 if (rowParams.tstyle) {
114711                     Ext.Error.raise("The getRowClass tstyle property is no longer supported.");
114712                 }
114713                 if (rowParams.cells) {
114714                     Ext.Error.raise("The getRowClass cells property is no longer supported.");
114715                 }
114716                 if (rowParams.body) {
114717                     Ext.Error.raise("The getRowClass body property is no longer supported. Use the getAdditionalData method of the rowbody feature.");
114718                 }
114719                 if (rowParams.bodyStyle) {
114720                     Ext.Error.raise("The getRowClass bodyStyle property is no longer supported.");
114721                 }
114722                 if (rowParams.cols) {
114723                     Ext.Error.raise("The getRowClass cols property is no longer supported.");
114724                 }
114725             }
114726         }
114727         // currently only one feature may implement collectData. This is to modify
114728         // what's returned to the view before its rendered
114729         for (; i < ln; i++) {
114730             feature = features[i];
114731             if (feature.isFeature && feature.collectData && !feature.disabled) {
114732                 o = feature.collectData(records, preppedRecords, startIndex, fullWidth, o);
114733                 break;
114734             }
114735         }
114736         return o;
114737     },
114738
114739     // TODO: Refactor header resizing to column resizing
114740     /**
114741      * When a header is resized, setWidth on the individual columns resizer class,
114742      * the top level table, save/restore scroll state, generate a new template and
114743      * restore focus to the grid view's element so that keyboard navigation
114744      * continues to work.
114745      * @private
114746      */
114747     onHeaderResize: function(header, w, suppressFocus) {
114748         var me = this,
114749             el = me.el;
114750
114751         if (el) {
114752             me.saveScrollState();
114753             // Grab the col and set the width, css
114754             // class is generated in TableChunker.
114755             // Select composites because there may be several chunks.
114756
114757             // IE6 and IE7 bug.
114758             // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
114759             // We need to increment the passed with in this case.
114760             if (Ext.isIE6 || Ext.isIE7) {
114761                 if (header.el.hasCls(Ext.baseCSSPrefix + 'column-header-first')) {
114762                     w += 1;
114763                 }
114764             }
114765             el.select('.' + Ext.baseCSSPrefix + 'grid-col-resizer-'+header.id).setWidth(w);
114766             el.select('.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(me.headerCt.getFullWidth());
114767             me.restoreScrollState();
114768             if (!me.ignoreTemplate) {
114769                 me.setNewTemplate();
114770             }
114771             if (!suppressFocus) {
114772                 me.el.focus();
114773             }
114774         }
114775     },
114776
114777     /**
114778      * When a header is shown restore its oldWidth if it was previously hidden.
114779      * @private
114780      */
114781     onHeaderShow: function(headerCt, header, suppressFocus) {
114782         var me = this;
114783         me.ignoreTemplate = true;
114784         // restore headers that were dynamically hidden
114785         if (header.oldWidth) {
114786             me.onHeaderResize(header, header.oldWidth, suppressFocus);
114787             delete header.oldWidth;
114788         // flexed headers will have a calculated size set
114789         // this additional check has to do with the fact that
114790         // defaults: {width: 100} will fight with a flex value
114791         } else if (header.width && !header.flex) {
114792             me.onHeaderResize(header, header.width, suppressFocus);
114793         }
114794         delete me.ignoreTemplate;
114795         me.setNewTemplate();
114796     },
114797
114798     /**
114799      * When the header hides treat it as a resize to 0.
114800      * @private
114801      */
114802     onHeaderHide: function(headerCt, header, suppressFocus) {
114803         this.onHeaderResize(header, 0, suppressFocus);
114804     },
114805
114806     /**
114807      * Set a new template based on the current columns displayed in the
114808      * grid.
114809      * @private
114810      */
114811     setNewTemplate: function() {
114812         var me = this,
114813             columns = me.headerCt.getColumnsForTpl(true);
114814
114815         me.tpl = me.getTableChunker().getTableTpl({
114816             columns: columns,
114817             features: me.features
114818         });
114819     },
114820
114821     /**
114822      * Returns the configured chunker or default of Ext.view.TableChunker
114823      */
114824     getTableChunker: function() {
114825         return this.chunker || Ext.view.TableChunker;
114826     },
114827
114828     /**
114829      * Adds a CSS Class to a specific row.
114830      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
114831      * representing this row
114832      * @param {String} cls
114833      */
114834     addRowCls: function(rowInfo, cls) {
114835         var row = this.getNode(rowInfo);
114836         if (row) {
114837             Ext.fly(row).addCls(cls);
114838         }
114839     },
114840
114841     /**
114842      * Removes a CSS Class from a specific row.
114843      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
114844      * representing this row
114845      * @param {String} cls
114846      */
114847     removeRowCls: function(rowInfo, cls) {
114848         var row = this.getNode(rowInfo);
114849         if (row) {
114850             Ext.fly(row).removeCls(cls);
114851         }
114852     },
114853
114854     // GridSelectionModel invokes onRowSelect as selection changes
114855     onRowSelect : function(rowIdx) {
114856         this.addRowCls(rowIdx, this.selectedItemCls);
114857     },
114858
114859     // GridSelectionModel invokes onRowDeselect as selection changes
114860     onRowDeselect : function(rowIdx) {
114861         var me = this;
114862
114863         me.removeRowCls(rowIdx, me.selectedItemCls);
114864         me.removeRowCls(rowIdx, me.focusedItemCls);
114865     },
114866
114867     onCellSelect: function(position) {
114868         var cell = this.getCellByPosition(position);
114869         if (cell) {
114870             cell.addCls(this.selectedCellCls);
114871         }
114872     },
114873
114874     onCellDeselect: function(position) {
114875         var cell = this.getCellByPosition(position);
114876         if (cell) {
114877             cell.removeCls(this.selectedCellCls);
114878         }
114879
114880     },
114881
114882     onCellFocus: function(position) {
114883         //var cell = this.getCellByPosition(position);
114884         this.focusCell(position);
114885     },
114886
114887     getCellByPosition: function(position) {
114888         var row    = position.row,
114889             column = position.column,
114890             store  = this.store,
114891             node   = this.getNode(row),
114892             header = this.headerCt.getHeaderAtIndex(column),
114893             cellSelector,
114894             cell = false;
114895
114896         if (header && node) {
114897             cellSelector = header.getCellSelector();
114898             cell = Ext.fly(node).down(cellSelector);
114899         }
114900         return cell;
114901     },
114902
114903     // GridSelectionModel invokes onRowFocus to 'highlight'
114904     // the last row focused
114905     onRowFocus: function(rowIdx, highlight, supressFocus) {
114906         var me = this,
114907             row = me.getNode(rowIdx);
114908
114909         if (highlight) {
114910             me.addRowCls(rowIdx, me.focusedItemCls);
114911             if (!supressFocus) {
114912                 me.focusRow(rowIdx);
114913             }
114914             //this.el.dom.setAttribute('aria-activedescendant', row.id);
114915         } else {
114916             me.removeRowCls(rowIdx, me.focusedItemCls);
114917         }
114918     },
114919
114920     /**
114921      * Focuses a particular row and brings it into view. Will fire the rowfocus event.
114922      * @param {HTMLElement/String/Number/Ext.data.Model} rowIdx
114923      * An HTMLElement template node, index of a template node, the id of a template node or the
114924      * record associated with the node.
114925      */
114926     focusRow: function(rowIdx) {
114927         var me         = this,
114928             row        = me.getNode(rowIdx),
114929             el         = me.el,
114930             adjustment = 0,
114931             panel      = me.ownerCt,
114932             rowRegion,
114933             elRegion,
114934             record;
114935
114936         if (row && el) {
114937             elRegion  = el.getRegion();
114938             rowRegion = Ext.fly(row).getRegion();
114939             // row is above
114940             if (rowRegion.top < elRegion.top) {
114941                 adjustment = rowRegion.top - elRegion.top;
114942             // row is below
114943             } else if (rowRegion.bottom > elRegion.bottom) {
114944                 adjustment = rowRegion.bottom - elRegion.bottom;
114945             }
114946             record = me.getRecord(row);
114947             rowIdx = me.store.indexOf(record);
114948
114949             if (adjustment) {
114950                 // scroll the grid itself, so that all gridview's update.
114951                 panel.scrollByDeltaY(adjustment);
114952             }
114953             me.fireEvent('rowfocus', record, row, rowIdx);
114954         }
114955     },
114956
114957     focusCell: function(position) {
114958         var me          = this,
114959             cell        = me.getCellByPosition(position),
114960             el          = me.el,
114961             adjustmentY = 0,
114962             adjustmentX = 0,
114963             elRegion    = el.getRegion(),
114964             panel       = me.ownerCt,
114965             cellRegion,
114966             record;
114967
114968         if (cell) {
114969             cellRegion = cell.getRegion();
114970             // cell is above
114971             if (cellRegion.top < elRegion.top) {
114972                 adjustmentY = cellRegion.top - elRegion.top;
114973             // cell is below
114974             } else if (cellRegion.bottom > elRegion.bottom) {
114975                 adjustmentY = cellRegion.bottom - elRegion.bottom;
114976             }
114977
114978             // cell is left
114979             if (cellRegion.left < elRegion.left) {
114980                 adjustmentX = cellRegion.left - elRegion.left;
114981             // cell is right
114982             } else if (cellRegion.right > elRegion.right) {
114983                 adjustmentX = cellRegion.right - elRegion.right;
114984             }
114985
114986             if (adjustmentY) {
114987                 // scroll the grid itself, so that all gridview's update.
114988                 panel.scrollByDeltaY(adjustmentY);
114989             }
114990             if (adjustmentX) {
114991                 panel.scrollByDeltaX(adjustmentX);
114992             }
114993             el.focus();
114994             me.fireEvent('cellfocus', record, cell, position);
114995         }
114996     },
114997
114998     /**
114999      * Scrolls by delta. This affects this individual view ONLY and does not
115000      * synchronize across views or scrollers.
115001      * @param {Number} delta
115002      * @param {String} dir (optional) Valid values are scrollTop and scrollLeft. Defaults to scrollTop.
115003      * @private
115004      */
115005     scrollByDelta: function(delta, dir) {
115006         dir = dir || 'scrollTop';
115007         var elDom = this.el.dom;
115008         elDom[dir] = (elDom[dir] += delta);
115009     },
115010
115011     onUpdate: function(ds, index) {
115012         this.callParent(arguments);
115013     },
115014
115015     /**
115016      * Saves the scrollState in a private variable. Must be used in conjunction with restoreScrollState
115017      */
115018     saveScrollState: function() {
115019         if (this.rendered) {
115020             var dom = this.el.dom, 
115021                 state = this.scrollState;
115022             
115023             state.left = dom.scrollLeft;
115024             state.top = dom.scrollTop;
115025         }
115026     },
115027
115028     /**
115029      * Restores the scrollState.
115030      * Must be used in conjunction with saveScrollState
115031      * @private
115032      */
115033     restoreScrollState: function() {
115034         if (this.rendered) {
115035             var dom = this.el.dom, 
115036                 state = this.scrollState, 
115037                 headerEl = this.headerCt.el.dom;
115038             
115039             headerEl.scrollLeft = dom.scrollLeft = state.left;
115040             dom.scrollTop = state.top;
115041         }
115042     },
115043
115044     /**
115045      * Refreshes the grid view. Saves and restores the scroll state, generates a new template, stripes rows and
115046      * invalidates the scrollers.
115047      */
115048     refresh: function() {
115049         this.setNewTemplate();
115050         this.callParent(arguments);
115051     },
115052
115053     processItemEvent: function(record, row, rowIndex, e) {
115054         var me = this,
115055             cell = e.getTarget(me.cellSelector, row),
115056             cellIndex = cell ? cell.cellIndex : -1,
115057             map = me.statics().EventMap,
115058             selModel = me.getSelectionModel(),
115059             type = e.type,
115060             result;
115061
115062         if (type == 'keydown' && !cell && selModel.getCurrentPosition) {
115063             // CellModel, otherwise we can't tell which cell to invoke
115064             cell = me.getCellByPosition(selModel.getCurrentPosition());
115065             if (cell) {
115066                 cell = cell.dom;
115067                 cellIndex = cell.cellIndex;
115068             }
115069         }
115070
115071         result = me.fireEvent('uievent', type, me, cell, rowIndex, cellIndex, e);
115072
115073         if (result === false || me.callParent(arguments) === false) {
115074             return false;
115075         }
115076
115077         // Don't handle cellmouseenter and cellmouseleave events for now
115078         if (type == 'mouseover' || type == 'mouseout') {
115079             return true;
115080         }
115081
115082         return !(
115083             // We are adding cell and feature events
115084             (me['onBeforeCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
115085             (me.fireEvent('beforecell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false) ||
115086             (me['onCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
115087             (me.fireEvent('cell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false)
115088         );
115089     },
115090
115091     processSpecialEvent: function(e) {
115092         var me = this,
115093             map = me.statics().EventMap,
115094             features = me.features,
115095             ln = features.length,
115096             type = e.type,
115097             i, feature, prefix, featureTarget,
115098             beforeArgs, args,
115099             panel = me.ownerCt;
115100
115101         me.callParent(arguments);
115102
115103         if (type == 'mouseover' || type == 'mouseout') {
115104             return;
115105         }
115106
115107         for (i = 0; i < ln; i++) {
115108             feature = features[i];
115109             if (feature.hasFeatureEvent) {
115110                 featureTarget = e.getTarget(feature.eventSelector, me.getTargetEl());
115111                 if (featureTarget) {
115112                     prefix = feature.eventPrefix;
115113                     // allows features to implement getFireEventArgs to change the
115114                     // fireEvent signature
115115                     beforeArgs = feature.getFireEventArgs('before' + prefix + type, me, featureTarget, e);
115116                     args = feature.getFireEventArgs(prefix + type, me, featureTarget, e);
115117
115118                     if (
115119                         // before view event
115120                         (me.fireEvent.apply(me, beforeArgs) === false) ||
115121                         // panel grid event
115122                         (panel.fireEvent.apply(panel, beforeArgs) === false) ||
115123                         // view event
115124                         (me.fireEvent.apply(me, args) === false) ||
115125                         // panel event
115126                         (panel.fireEvent.apply(panel, args) === false)
115127                     ) {
115128                         return false;
115129                     }
115130                 }
115131             }
115132         }
115133         return true;
115134     },
115135
115136     onCellMouseDown: Ext.emptyFn,
115137     onCellMouseUp: Ext.emptyFn,
115138     onCellClick: Ext.emptyFn,
115139     onCellDblClick: Ext.emptyFn,
115140     onCellContextMenu: Ext.emptyFn,
115141     onCellKeyDown: Ext.emptyFn,
115142     onBeforeCellMouseDown: Ext.emptyFn,
115143     onBeforeCellMouseUp: Ext.emptyFn,
115144     onBeforeCellClick: Ext.emptyFn,
115145     onBeforeCellDblClick: Ext.emptyFn,
115146     onBeforeCellContextMenu: Ext.emptyFn,
115147     onBeforeCellKeyDown: Ext.emptyFn,
115148
115149     /**
115150      * Expands a particular header to fit the max content width.
115151      * This will ONLY expand, not contract.
115152      * @private
115153      */
115154     expandToFit: function(header) {
115155         if (header) {
115156             var maxWidth = this.getMaxContentWidth(header);
115157             delete header.flex;
115158             header.setWidth(maxWidth);
115159         }
115160     },
115161
115162     /**
115163      * Returns the max contentWidth of the header's text and all cells
115164      * in the grid under this header.
115165      * @private
115166      */
115167     getMaxContentWidth: function(header) {
115168         var cellSelector = header.getCellInnerSelector(),
115169             cells        = this.el.query(cellSelector),
115170             i = 0,
115171             ln = cells.length,
115172             maxWidth = header.el.dom.scrollWidth,
115173             scrollWidth;
115174
115175         for (; i < ln; i++) {
115176             scrollWidth = cells[i].scrollWidth;
115177             if (scrollWidth > maxWidth) {
115178                 maxWidth = scrollWidth;
115179             }
115180         }
115181         return maxWidth;
115182     },
115183
115184     getPositionByEvent: function(e) {
115185         var me       = this,
115186             cellNode = e.getTarget(me.cellSelector),
115187             rowNode  = e.getTarget(me.itemSelector),
115188             record   = me.getRecord(rowNode),
115189             header   = me.getHeaderByCell(cellNode);
115190
115191         return me.getPosition(record, header);
115192     },
115193
115194     getHeaderByCell: function(cell) {
115195         if (cell) {
115196             var m = cell.className.match(this.cellRe);
115197             if (m && m[1]) {
115198                 return Ext.getCmp(m[1]);
115199             }
115200         }
115201         return false;
115202     },
115203
115204     /**
115205      * @param {Object} position The current row and column: an object containing the following properties:
115206      *
115207      * - row - The row index
115208      * - column - The column index
115209      *
115210      * @param {String} direction 'up', 'down', 'right' and 'left'
115211      * @param {Ext.EventObject} e event
115212      * @param {Boolean} preventWrap Set to true to prevent wrap around to the next or previous row.
115213      * @param {Function} verifierFn A function to verify the validity of the calculated position.
115214      * When using this function, you must return true to allow the newPosition to be returned.
115215      * @param {Object} scope Scope to run the verifierFn in
115216      * @returns {Object} newPosition An object containing the following properties:
115217      *
115218      * - row - The row index
115219      * - column - The column index
115220      *
115221      * @private
115222      */
115223     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
115224         var me       = this,
115225             row      = pos.row,
115226             column   = pos.column,
115227             rowCount = me.store.getCount(),
115228             firstCol = me.getFirstVisibleColumnIndex(),
115229             lastCol  = me.getLastVisibleColumnIndex(),
115230             newPos   = {row: row, column: column},
115231             activeHeader = me.headerCt.getHeaderAtIndex(column);
115232
115233         // no active header or its currently hidden
115234         if (!activeHeader || activeHeader.hidden) {
115235             return false;
115236         }
115237
115238         e = e || {};
115239         direction = direction.toLowerCase();
115240         switch (direction) {
115241             case 'right':
115242                 // has the potential to wrap if its last
115243                 if (column === lastCol) {
115244                     // if bottom row and last column, deny right
115245                     if (preventWrap || row === rowCount - 1) {
115246                         return false;
115247                     }
115248                     if (!e.ctrlKey) {
115249                         // otherwise wrap to nextRow and firstCol
115250                         newPos.row = row + 1;
115251                         newPos.column = firstCol;
115252                     }
115253                 // go right
115254                 } else {
115255                     if (!e.ctrlKey) {
115256                         newPos.column = column + me.getRightGap(activeHeader);
115257                     } else {
115258                         newPos.column = lastCol;
115259                     }
115260                 }
115261                 break;
115262
115263             case 'left':
115264                 // has the potential to wrap
115265                 if (column === firstCol) {
115266                     // if top row and first column, deny left
115267                     if (preventWrap || row === 0) {
115268                         return false;
115269                     }
115270                     if (!e.ctrlKey) {
115271                         // otherwise wrap to prevRow and lastCol
115272                         newPos.row = row - 1;
115273                         newPos.column = lastCol;
115274                     }
115275                 // go left
115276                 } else {
115277                     if (!e.ctrlKey) {
115278                         newPos.column = column + me.getLeftGap(activeHeader);
115279                     } else {
115280                         newPos.column = firstCol;
115281                     }
115282                 }
115283                 break;
115284
115285             case 'up':
115286                 // if top row, deny up
115287                 if (row === 0) {
115288                     return false;
115289                 // go up
115290                 } else {
115291                     if (!e.ctrlKey) {
115292                         newPos.row = row - 1;
115293                     } else {
115294                         newPos.row = 0;
115295                     }
115296                 }
115297                 break;
115298
115299             case 'down':
115300                 // if bottom row, deny down
115301                 if (row === rowCount - 1) {
115302                     return false;
115303                 // go down
115304                 } else {
115305                     if (!e.ctrlKey) {
115306                         newPos.row = row + 1;
115307                     } else {
115308                         newPos.row = rowCount - 1;
115309                     }
115310                 }
115311                 break;
115312         }
115313
115314         if (verifierFn && verifierFn.call(scope || window, newPos) !== true) {
115315             return false;
115316         } else {
115317             return newPos;
115318         }
115319     },
115320     getFirstVisibleColumnIndex: function() {
115321         var headerCt   = this.getHeaderCt(),
115322             allColumns = headerCt.getGridColumns(),
115323             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
115324             firstHeader = visHeaders[0];
115325
115326         return headerCt.getHeaderIndex(firstHeader);
115327     },
115328
115329     getLastVisibleColumnIndex: function() {
115330         var headerCt   = this.getHeaderCt(),
115331             allColumns = headerCt.getGridColumns(),
115332             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
115333             lastHeader = visHeaders[visHeaders.length - 1];
115334
115335         return headerCt.getHeaderIndex(lastHeader);
115336     },
115337
115338     getHeaderCt: function() {
115339         return this.headerCt;
115340     },
115341
115342     getPosition: function(record, header) {
115343         var me = this,
115344             store = me.store,
115345             gridCols = me.headerCt.getGridColumns();
115346
115347         return {
115348             row: store.indexOf(record),
115349             column: Ext.Array.indexOf(gridCols, header)
115350         };
115351     },
115352
115353     /**
115354      * Determines the 'gap' between the closest adjacent header to the right
115355      * that is not hidden.
115356      * @private
115357      */
115358     getRightGap: function(activeHeader) {
115359         var headerCt        = this.getHeaderCt(),
115360             headers         = headerCt.getGridColumns(),
115361             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
115362             i               = activeHeaderIdx + 1,
115363             nextIdx;
115364
115365         for (; i <= headers.length; i++) {
115366             if (!headers[i].hidden) {
115367                 nextIdx = i;
115368                 break;
115369             }
115370         }
115371
115372         return nextIdx - activeHeaderIdx;
115373     },
115374
115375     beforeDestroy: function() {
115376         if (this.rendered) {
115377             this.el.removeAllListeners();
115378         }
115379         this.callParent(arguments);
115380     },
115381
115382     /**
115383      * Determines the 'gap' between the closest adjacent header to the left
115384      * that is not hidden.
115385      * @private
115386      */
115387     getLeftGap: function(activeHeader) {
115388         var headerCt        = this.getHeaderCt(),
115389             headers         = headerCt.getGridColumns(),
115390             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
115391             i               = activeHeaderIdx - 1,
115392             prevIdx;
115393
115394         for (; i >= 0; i--) {
115395             if (!headers[i].hidden) {
115396                 prevIdx = i;
115397                 break;
115398             }
115399         }
115400
115401         return prevIdx - activeHeaderIdx;
115402     }
115403 });
115404 /**
115405  * @class Ext.grid.View
115406  * @extends Ext.view.Table
115407  *
115408  * The grid View class provides extra {@link Ext.grid.Panel} specific functionality to the
115409  * {@link Ext.view.Table}. In general, this class is not instanced directly, instead a viewConfig
115410  * option is passed to the grid:
115411  *
115412  *     Ext.create('Ext.grid.Panel', {
115413  *         // other options
115414  *         viewConfig: {
115415  *             stripeRows: false
115416  *         }
115417  *     });
115418  *
115419  * ## Drag Drop
115420  *
115421  * Drag and drop functionality can be achieved in the grid by attaching a {@link Ext.grid.plugin.DragDrop} plugin
115422  * when creating the view.
115423  *
115424  *     Ext.create('Ext.grid.Panel', {
115425  *         // other options
115426  *         viewConfig: {
115427  *             plugins: {
115428  *                 ddGroup: 'people-group',
115429  *                 ptype: 'gridviewdragdrop',
115430  *                 enableDrop: false
115431  *             }
115432  *         }
115433  *     });
115434  */
115435 Ext.define('Ext.grid.View', {
115436     extend: 'Ext.view.Table',
115437     alias: 'widget.gridview',
115438
115439     /**
115440      * @cfg {Boolean} stripeRows <tt>true</tt> to stripe the rows. Default is <tt>true</tt>.
115441      * <p>This causes the CSS class <tt><b>x-grid-row-alt</b></tt> to be added to alternate rows of
115442      * the grid. A default CSS rule is provided which sets a background color, but you can override this
115443      * with a rule which either overrides the <b>background-color</b> style using the '!important'
115444      * modifier, or which uses a CSS selector of higher specificity.</p>
115445      */
115446     stripeRows: true,
115447
115448     invalidateScrollerOnRefresh: true,
115449
115450     /**
115451      * Scroll the GridView to the top by scrolling the scroller.
115452      * @private
115453      */
115454     scrollToTop : function(){
115455         if (this.rendered) {
115456             var section = this.ownerCt,
115457                 verticalScroller = section.verticalScroller;
115458
115459             if (verticalScroller) {
115460                 verticalScroller.scrollToTop();
115461             }
115462         }
115463     },
115464
115465     // after adding a row stripe rows from then on
115466     onAdd: function(ds, records, index) {
115467         this.callParent(arguments);
115468         this.doStripeRows(index);
115469     },
115470
115471     // after removing a row stripe rows from then on
115472     onRemove: function(ds, records, index) {
115473         this.callParent(arguments);
115474         this.doStripeRows(index);
115475     },
115476
115477     onUpdate: function(ds, record, operation) {
115478         var index = ds.indexOf(record);
115479         this.callParent(arguments);
115480         this.doStripeRows(index, index);
115481     },
115482
115483     /**
115484      * Stripe rows from a particular row index
115485      * @param {Number} startRow
115486      * @param {Number} endRow (Optional) argument specifying the last row to process. By default process up to the last row.
115487      * @private
115488      */
115489     doStripeRows: function(startRow, endRow) {
115490         // ensure stripeRows configuration is turned on
115491         if (this.stripeRows) {
115492             var rows   = this.getNodes(startRow, endRow),
115493                 rowsLn = rows.length,
115494                 i      = 0,
115495                 row;
115496
115497             for (; i < rowsLn; i++) {
115498                 row = rows[i];
115499                 // Remove prior applied row classes.
115500                 row.className = row.className.replace(this.rowClsRe, ' ');
115501                 startRow++;
115502                 // Every odd row will get an additional cls
115503                 if (startRow % 2 === 0) {
115504                     row.className += (' ' + this.altRowCls);
115505                 }
115506             }
115507         }
115508     },
115509
115510     refresh: function(firstPass) {
115511         this.callParent(arguments);
115512         this.doStripeRows(0);
115513         // TODO: Remove gridpanel dependency
115514         var g = this.up('gridpanel');
115515         if (g && this.invalidateScrollerOnRefresh) {
115516             g.invalidateScroller();
115517         }
115518     }
115519 });
115520
115521 /**
115522  * @author Aaron Conran
115523  * @docauthor Ed Spencer
115524  *
115525  * Grids are an excellent way of showing large amounts of tabular data on the client side. Essentially a supercharged
115526  * `<table>`, GridPanel makes it easy to fetch, sort and filter large amounts of data.
115527  *
115528  * Grids are composed of two main pieces - a {@link Ext.data.Store Store} full of data and a set of columns to render.
115529  *
115530  * ## Basic GridPanel
115531  *
115532  *     @example
115533  *     Ext.create('Ext.data.Store', {
115534  *         storeId:'simpsonsStore',
115535  *         fields:['name', 'email', 'phone'],
115536  *         data:{'items':[
115537  *             { 'name': 'Lisa',  "email":"lisa@simpsons.com",  "phone":"555-111-1224"  },
115538  *             { 'name': 'Bart',  "email":"bart@simpsons.com",  "phone":"555-222-1234" },
115539  *             { 'name': 'Homer', "email":"home@simpsons.com",  "phone":"555-222-1244"  },
115540  *             { 'name': 'Marge', "email":"marge@simpsons.com", "phone":"555-222-1254"  }
115541  *         ]},
115542  *         proxy: {
115543  *             type: 'memory',
115544  *             reader: {
115545  *                 type: 'json',
115546  *                 root: 'items'
115547  *             }
115548  *         }
115549  *     });
115550  *
115551  *     Ext.create('Ext.grid.Panel', {
115552  *         title: 'Simpsons',
115553  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
115554  *         columns: [
115555  *             { header: 'Name',  dataIndex: 'name' },
115556  *             { header: 'Email', dataIndex: 'email', flex: 1 },
115557  *             { header: 'Phone', dataIndex: 'phone' }
115558  *         ],
115559  *         height: 200,
115560  *         width: 400,
115561  *         renderTo: Ext.getBody()
115562  *     });
115563  *
115564  * The code above produces a simple grid with three columns. We specified a Store which will load JSON data inline.
115565  * In most apps we would be placing the grid inside another container and wouldn't need to use the
115566  * {@link #height}, {@link #width} and {@link #renderTo} configurations but they are included here to make it easy to get
115567  * up and running.
115568  *
115569  * The grid we created above will contain a header bar with a title ('Simpsons'), a row of column headers directly underneath
115570  * and finally the grid rows under the headers.
115571  *
115572  * ## Configuring columns
115573  *
115574  * By default, each column is sortable and will toggle between ASC and DESC sorting when you click on its header. Each
115575  * column header is also reorderable by default, and each gains a drop-down menu with options to hide and show columns.
115576  * It's easy to configure each column - here we use the same example as above and just modify the columns config:
115577  *
115578  *     columns: [
115579  *         {
115580  *             header: 'Name',
115581  *             dataIndex: 'name',
115582  *             sortable: false,
115583  *             hideable: false,
115584  *             flex: 1
115585  *         },
115586  *         {
115587  *             header: 'Email',
115588  *             dataIndex: 'email',
115589  *             hidden: true
115590  *         },
115591  *         {
115592  *             header: 'Phone',
115593  *             dataIndex: 'phone',
115594  *             width: 100
115595  *         }
115596  *     ]
115597  *
115598  * We turned off sorting and hiding on the 'Name' column so clicking its header now has no effect. We also made the Email
115599  * column hidden by default (it can be shown again by using the menu on any other column). We also set the Phone column to
115600  * a fixed with of 100px and flexed the Name column, which means it takes up all remaining width after the other columns
115601  * have been accounted for. See the {@link Ext.grid.column.Column column docs} for more details.
115602  *
115603  * ## Renderers
115604  *
115605  * As well as customizing columns, it's easy to alter the rendering of individual cells using renderers. A renderer is
115606  * tied to a particular column and is passed the value that would be rendered into each cell in that column. For example,
115607  * we could define a renderer function for the email column to turn each email address into a mailto link:
115608  *
115609  *     columns: [
115610  *         {
115611  *             header: 'Email',
115612  *             dataIndex: 'email',
115613  *             renderer: function(value) {
115614  *                 return Ext.String.format('<a href="mailto:{0}">{1}</a>', value, value);
115615  *             }
115616  *         }
115617  *     ]
115618  *
115619  * See the {@link Ext.grid.column.Column column docs} for more information on renderers.
115620  *
115621  * ## Selection Models
115622  *
115623  * Sometimes all you want is to render data onto the screen for viewing, but usually it's necessary to interact with or
115624  * update that data. Grids use a concept called a Selection Model, which is simply a mechanism for selecting some part of
115625  * the data in the grid. The two main types of Selection Model are RowSelectionModel, where entire rows are selected, and
115626  * CellSelectionModel, where individual cells are selected.
115627  *
115628  * Grids use a Row Selection Model by default, but this is easy to customise like so:
115629  *
115630  *     Ext.create('Ext.grid.Panel', {
115631  *         selType: 'cellmodel',
115632  *         store: ...
115633  *     });
115634  *
115635  * Specifying the `cellmodel` changes a couple of things. Firstly, clicking on a cell now
115636  * selects just that cell (using a {@link Ext.selection.RowModel rowmodel} will select the entire row), and secondly the
115637  * keyboard navigation will walk from cell to cell instead of row to row. Cell-based selection models are usually used in
115638  * conjunction with editing.
115639  *
115640  * ## Editing
115641  *
115642  * Grid has built-in support for in-line editing. There are two chief editing modes - cell editing and row editing. Cell
115643  * editing is easy to add to your existing column setup - here we'll just modify the example above to include an editor
115644  * on both the name and the email columns:
115645  *
115646  *     Ext.create('Ext.grid.Panel', {
115647  *         title: 'Simpsons',
115648  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
115649  *         columns: [
115650  *             { header: 'Name',  dataIndex: 'name', field: 'textfield' },
115651  *             { header: 'Email', dataIndex: 'email', flex: 1,
115652  *                 field: {
115653  *                     xtype: 'textfield',
115654  *                     allowBlank: false
115655  *                 }
115656  *             },
115657  *             { header: 'Phone', dataIndex: 'phone' }
115658  *         ],
115659  *         selType: 'cellmodel',
115660  *         plugins: [
115661  *             Ext.create('Ext.grid.plugin.CellEditing', {
115662  *                 clicksToEdit: 1
115663  *             })
115664  *         ],
115665  *         height: 200,
115666  *         width: 400,
115667  *         renderTo: Ext.getBody()
115668  *     });
115669  *
115670  * This requires a little explanation. We're passing in {@link #store store} and {@link #columns columns} as normal, but
115671  * this time we've also specified a {@link Ext.grid.column.Column#field field} on two of our columns. For the Name column
115672  * we just want a default textfield to edit the value, so we specify 'textfield'. For the Email column we customized the
115673  * editor slightly by passing allowBlank: false, which will provide inline validation.
115674  *
115675  * To support cell editing, we also specified that the grid should use the 'cellmodel' {@link #selType}, and created an
115676  * instance of the {@link Ext.grid.plugin.CellEditing CellEditing plugin}, which we configured to activate each editor after a
115677  * single click.
115678  *
115679  * ## Row Editing
115680  *
115681  * The other type of editing is row-based editing, using the RowEditor component. This enables you to edit an entire row
115682  * at a time, rather than editing cell by cell. Row Editing works in exactly the same way as cell editing, all we need to
115683  * do is change the plugin type to {@link Ext.grid.plugin.RowEditing}, and set the selType to 'rowmodel':
115684  *
115685  *     Ext.create('Ext.grid.Panel', {
115686  *         title: 'Simpsons',
115687  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
115688  *         columns: [
115689  *             { header: 'Name',  dataIndex: 'name', field: 'textfield' },
115690  *             { header: 'Email', dataIndex: 'email', flex:1,
115691  *                 field: {
115692  *                     xtype: 'textfield',
115693  *                     allowBlank: false
115694  *                 }
115695  *             },
115696  *             { header: 'Phone', dataIndex: 'phone' }
115697  *         ],
115698  *         selType: 'rowmodel',
115699  *         plugins: [
115700  *             Ext.create('Ext.grid.plugin.RowEditing', {
115701  *                 clicksToEdit: 1
115702  *             })
115703  *         ],
115704  *         height: 200,
115705  *         width: 400,
115706  *         renderTo: Ext.getBody()
115707  *     });
115708  *
115709  * Again we passed some configuration to our {@link Ext.grid.plugin.RowEditing} plugin, and now when we click each row a row
115710  * editor will appear and enable us to edit each of the columns we have specified an editor for.
115711  *
115712  * ## Sorting & Filtering
115713  *
115714  * Every grid is attached to a {@link Ext.data.Store Store}, which provides multi-sort and filtering capabilities. It's
115715  * easy to set up a grid to be sorted from the start:
115716  *
115717  *     var myGrid = Ext.create('Ext.grid.Panel', {
115718  *         store: {
115719  *             fields: ['name', 'email', 'phone'],
115720  *             sorters: ['name', 'phone']
115721  *         },
115722  *         columns: [
115723  *             { text: 'Name',  dataIndex: 'name' },
115724  *             { text: 'Email', dataIndex: 'email' }
115725  *         ]
115726  *     });
115727  *
115728  * Sorting at run time is easily accomplished by simply clicking each column header. If you need to perform sorting on
115729  * more than one field at run time it's easy to do so by adding new sorters to the store:
115730  *
115731  *     myGrid.store.sort([
115732  *         { property: 'name',  direction: 'ASC' },
115733  *         { property: 'email', direction: 'DESC' }
115734  *     ]);
115735  *
115736  * See {@link Ext.data.Store} for examples of filtering.
115737  *
115738  * ## Grouping
115739  *
115740  * Grid supports the grouping of rows by any field. For example if we had a set of employee records, we might want to
115741  * group by the department that each employee works in. Here's how we might set that up:
115742  *
115743  *     @example
115744  *     var store = Ext.create('Ext.data.Store', {
115745  *         storeId:'employeeStore',
115746  *         fields:['name', 'senority', 'department'],
115747  *         groupField: 'department',
115748  *         data: {'employees':[
115749  *             { "name": "Michael Scott",  "senority": 7, "department": "Manangement" },
115750  *             { "name": "Dwight Schrute", "senority": 2, "department": "Sales" },
115751  *             { "name": "Jim Halpert",    "senority": 3, "department": "Sales" },
115752  *             { "name": "Kevin Malone",   "senority": 4, "department": "Accounting" },
115753  *             { "name": "Angela Martin",  "senority": 5, "department": "Accounting" }
115754  *         ]},
115755  *         proxy: {
115756  *             type: 'memory',
115757  *             reader: {
115758  *                 type: 'json',
115759  *                 root: 'employees'
115760  *             }
115761  *         }
115762  *     });
115763  *
115764  *     Ext.create('Ext.grid.Panel', {
115765  *         title: 'Employees',
115766  *         store: Ext.data.StoreManager.lookup('employeeStore'),
115767  *         columns: [
115768  *             { header: 'Name',     dataIndex: 'name' },
115769  *             { header: 'Senority', dataIndex: 'senority' }
115770  *         ],
115771  *         features: [{ftype:'grouping'}],
115772  *         width: 200,
115773  *         height: 275,
115774  *         renderTo: Ext.getBody()
115775  *     });
115776  *
115777  * ## Infinite Scrolling
115778  *
115779  * Grid supports infinite scrolling as an alternative to using a paging toolbar. Your users can scroll through thousands
115780  * of records without the performance penalties of renderering all the records on screen at once. The grid should be bound
115781  * to a store with a pageSize specified.
115782  *
115783  *     var grid = Ext.create('Ext.grid.Panel', {
115784  *         // Use a PagingGridScroller (this is interchangeable with a PagingToolbar)
115785  *         verticalScrollerType: 'paginggridscroller',
115786  *         // do not reset the scrollbar when the view refreshs
115787  *         invalidateScrollerOnRefresh: false,
115788  *         // infinite scrolling does not support selection
115789  *         disableSelection: true,
115790  *         // ...
115791  *     });
115792  *
115793  * ## Paging
115794  *
115795  * Grid supports paging through large sets of data via a PagingToolbar or PagingGridScroller (see the Infinite Scrolling section above).
115796  * To leverage paging via a toolbar or scroller, you need to set a pageSize configuration on the Store.
115797  *
115798  *     @example
115799  *     var itemsPerPage = 2;   // set the number of items you want per page
115800  *
115801  *     var store = Ext.create('Ext.data.Store', {
115802  *         id:'simpsonsStore',
115803  *         autoLoad: false,
115804  *         fields:['name', 'email', 'phone'],
115805  *         pageSize: itemsPerPage, // items per page
115806  *         proxy: {
115807  *             type: 'ajax',
115808  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
115809  *             reader: {
115810  *                 type: 'json',
115811  *                 root: 'items',
115812  *                 totalProperty: 'total'
115813  *             }
115814  *         }
115815  *     });
115816  *
115817  *     // specify segment of data you want to load using params
115818  *     store.load({
115819  *         params:{
115820  *             start:0,
115821  *             limit: itemsPerPage
115822  *         }
115823  *     });
115824  *
115825  *     Ext.create('Ext.grid.Panel', {
115826  *         title: 'Simpsons',
115827  *         store: store,
115828  *         columns: [
115829  *             {header: 'Name',  dataIndex: 'name'},
115830  *             {header: 'Email', dataIndex: 'email', flex:1},
115831  *             {header: 'Phone', dataIndex: 'phone'}
115832  *         ],
115833  *         width: 400,
115834  *         height: 125,
115835  *         dockedItems: [{
115836  *             xtype: 'pagingtoolbar',
115837  *             store: store,   // same store GridPanel is using
115838  *             dock: 'bottom',
115839  *             displayInfo: true
115840  *         }],
115841  *         renderTo: Ext.getBody()
115842  *     });
115843  */
115844 Ext.define('Ext.grid.Panel', {
115845     extend: 'Ext.panel.Table',
115846     requires: ['Ext.grid.View'],
115847     alias: ['widget.gridpanel', 'widget.grid'],
115848     alternateClassName: ['Ext.list.ListView', 'Ext.ListView', 'Ext.grid.GridPanel'],
115849     viewType: 'gridview',
115850
115851     lockable: false,
115852
115853     // Required for the Lockable Mixin. These are the configurations which will be copied to the
115854     // normal and locked sub tablepanels
115855     normalCfgCopy: ['invalidateScrollerOnRefresh', 'verticalScroller', 'verticalScrollDock', 'verticalScrollerType', 'scroll'],
115856     lockedCfgCopy: ['invalidateScrollerOnRefresh'],
115857
115858     /**
115859      * @cfg {Boolean} [columnLines=false] Adds column line styling
115860      */
115861
115862     initComponent: function() {
115863         var me = this;
115864
115865         if (me.columnLines) {
115866             me.setColumnLines(me.columnLines);
115867         }
115868
115869         me.callParent();
115870     },
115871
115872     setColumnLines: function(show) {
115873         var me = this,
115874             method = (show) ? 'addClsWithUI' : 'removeClsWithUI';
115875
115876         me[method]('with-col-lines');
115877     }
115878 });
115879
115880 // Currently has the following issues:
115881 // - Does not handle postEditValue
115882 // - Fields without editors need to sync with their values in Store
115883 // - starting to edit another record while already editing and dirty should probably prevent it
115884 // - aggregating validation messages
115885 // - tabIndex is not managed bc we leave elements in dom, and simply move via positioning
115886 // - layout issues when changing sizes/width while hidden (layout bug)
115887
115888 /**
115889  * @class Ext.grid.RowEditor
115890  * @extends Ext.form.Panel
115891  *
115892  * Internal utility class used to provide row editing functionality. For developers, they should use
115893  * the RowEditing plugin to use this functionality with a grid.
115894  *
115895  * @ignore
115896  */
115897 Ext.define('Ext.grid.RowEditor', {
115898     extend: 'Ext.form.Panel',
115899     requires: [
115900         'Ext.tip.ToolTip',
115901         'Ext.util.HashMap',
115902         'Ext.util.KeyNav'
115903     ],
115904
115905     saveBtnText  : 'Update',
115906     cancelBtnText: 'Cancel',
115907     errorsText: 'Errors',
115908     dirtyText: 'You need to commit or cancel your changes',
115909
115910     lastScrollLeft: 0,
115911     lastScrollTop: 0,
115912
115913     border: false,
115914     
115915     // Change the hideMode to offsets so that we get accurate measurements when
115916     // the roweditor is hidden for laying out things like a TriggerField.
115917     hideMode: 'offsets',
115918
115919     initComponent: function() {
115920         var me = this,
115921             form;
115922
115923         me.cls = Ext.baseCSSPrefix + 'grid-row-editor';
115924
115925         me.layout = {
115926             type: 'hbox',
115927             align: 'middle'
115928         };
115929
115930         // Maintain field-to-column mapping
115931         // It's easy to get a field from a column, but not vice versa
115932         me.columns = Ext.create('Ext.util.HashMap');
115933         me.columns.getKey = function(columnHeader) {
115934             var f;
115935             if (columnHeader.getEditor) {
115936                 f = columnHeader.getEditor();
115937                 if (f) {
115938                     return f.id;
115939                 }
115940             }
115941             return columnHeader.id;
115942         };
115943         me.mon(me.columns, {
115944             add: me.onFieldAdd,
115945             remove: me.onFieldRemove,
115946             replace: me.onFieldReplace,
115947             scope: me
115948         });
115949
115950         me.callParent(arguments);
115951
115952         if (me.fields) {
115953             me.setField(me.fields);
115954             delete me.fields;
115955         }
115956
115957         form = me.getForm();
115958         form.trackResetOnLoad = true;
115959     },
115960
115961     onFieldChange: function() {
115962         var me = this,
115963             form = me.getForm(),
115964             valid = form.isValid();
115965         if (me.errorSummary && me.isVisible()) {
115966             me[valid ? 'hideToolTip' : 'showToolTip']();
115967         }
115968         if (me.floatingButtons) {
115969             me.floatingButtons.child('#update').setDisabled(!valid);
115970         }
115971         me.isValid = valid;
115972     },
115973
115974     afterRender: function() {
115975         var me = this,
115976             plugin = me.editingPlugin;
115977
115978         me.callParent(arguments);
115979         me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });
115980
115981         // Prevent from bubbling click events to the grid view
115982         me.mon(me.el, {
115983             click: Ext.emptyFn,
115984             stopPropagation: true
115985         });
115986
115987         me.el.swallowEvent([
115988             'keypress',
115989             'keydown'
115990         ]);
115991
115992         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
115993             enter: plugin.completeEdit,
115994             esc: plugin.onEscKey,
115995             scope: plugin
115996         });
115997
115998         me.mon(plugin.view, {
115999             beforerefresh: me.onBeforeViewRefresh,
116000             refresh: me.onViewRefresh,
116001             scope: me
116002         });
116003     },
116004
116005     onBeforeViewRefresh: function(view) {
116006         var me = this,
116007             viewDom = view.el.dom;
116008
116009         if (me.el.dom.parentNode === viewDom) {
116010             viewDom.removeChild(me.el.dom);
116011         }
116012     },
116013
116014     onViewRefresh: function(view) {
116015         var me = this,
116016             viewDom = view.el.dom,
116017             context = me.context,
116018             idx;
116019
116020         viewDom.appendChild(me.el.dom);
116021
116022         // Recover our row node after a view refresh
116023         if (context && (idx = context.store.indexOf(context.record)) >= 0) {
116024             context.row = view.getNode(idx);
116025             me.reposition();
116026             if (me.tooltip && me.tooltip.isVisible()) {
116027                 me.tooltip.setTarget(context.row);
116028             }
116029         } else {
116030             me.editingPlugin.cancelEdit();
116031         }
116032     },
116033
116034     onCtScroll: function(e, target) {
116035         var me = this,
116036             scrollTop  = target.scrollTop,
116037             scrollLeft = target.scrollLeft;
116038
116039         if (scrollTop !== me.lastScrollTop) {
116040             me.lastScrollTop = scrollTop;
116041             if ((me.tooltip && me.tooltip.isVisible()) || me.hiddenTip) {
116042                 me.repositionTip();
116043             }
116044         }
116045         if (scrollLeft !== me.lastScrollLeft) {
116046             me.lastScrollLeft = scrollLeft;
116047             me.reposition();
116048         }
116049     },
116050
116051     onColumnAdd: function(column) {
116052         this.setField(column);
116053     },
116054
116055     onColumnRemove: function(column) {
116056         this.columns.remove(column);
116057     },
116058
116059     onColumnResize: function(column, width) {
116060         column.getEditor().setWidth(width - 2);
116061         if (this.isVisible()) {
116062             this.reposition();
116063         }
116064     },
116065
116066     onColumnHide: function(column) {
116067         column.getEditor().hide();
116068         if (this.isVisible()) {
116069             this.reposition();
116070         }
116071     },
116072
116073     onColumnShow: function(column) {
116074         var field = column.getEditor();
116075         field.setWidth(column.getWidth() - 2).show();
116076         if (this.isVisible()) {
116077             this.reposition();
116078         }
116079     },
116080
116081     onColumnMove: function(column, fromIdx, toIdx) {
116082         var field = column.getEditor();
116083         if (this.items.indexOf(field) != toIdx) {
116084             this.move(fromIdx, toIdx);
116085         }
116086     },
116087
116088     onFieldAdd: function(map, fieldId, column) {
116089         var me = this,
116090             colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column),
116091             field = column.getEditor({ xtype: 'displayfield' });
116092
116093         me.insert(colIdx, field);
116094     },
116095
116096     onFieldRemove: function(map, fieldId, column) {
116097         var me = this,
116098             field = column.getEditor(),
116099             fieldEl = field.el;
116100         me.remove(field, false);
116101         if (fieldEl) {
116102             fieldEl.remove();
116103         }
116104     },
116105
116106     onFieldReplace: function(map, fieldId, column, oldColumn) {
116107         var me = this;
116108         me.onFieldRemove(map, fieldId, oldColumn);
116109     },
116110
116111     clearFields: function() {
116112         var me = this,
116113             map = me.columns;
116114         map.each(function(fieldId) {
116115             map.removeAtKey(fieldId);
116116         });
116117     },
116118
116119     getFloatingButtons: function() {
116120         var me = this,
116121             cssPrefix = Ext.baseCSSPrefix,
116122             btnsCss = cssPrefix + 'grid-row-editor-buttons',
116123             plugin = me.editingPlugin,
116124             btns;
116125
116126         if (!me.floatingButtons) {
116127             btns = me.floatingButtons = Ext.create('Ext.Container', {
116128                 renderTpl: [
116129                     '<div class="{baseCls}-ml"></div>',
116130                     '<div class="{baseCls}-mr"></div>',
116131                     '<div class="{baseCls}-bl"></div>',
116132                     '<div class="{baseCls}-br"></div>',
116133                     '<div class="{baseCls}-bc"></div>'
116134                 ],
116135
116136                 renderTo: me.el,
116137                 baseCls: btnsCss,
116138                 layout: {
116139                     type: 'hbox',
116140                     align: 'middle'
116141                 },
116142                 defaults: {
116143                     margins: '0 1 0 1'
116144                 },
116145                 items: [{
116146                     itemId: 'update',
116147                     flex: 1,
116148                     xtype: 'button',
116149                     handler: plugin.completeEdit,
116150                     scope: plugin,
116151                     text: me.saveBtnText,
116152                     disabled: !me.isValid
116153                 }, {
116154                     flex: 1,
116155                     xtype: 'button',
116156                     handler: plugin.cancelEdit,
116157                     scope: plugin,
116158                     text: me.cancelBtnText
116159                 }]
116160             });
116161
116162             // Prevent from bubbling click events to the grid view
116163             me.mon(btns.el, {
116164                 // BrowserBug: Opera 11.01
116165                 //   causes the view to scroll when a button is focused from mousedown
116166                 mousedown: Ext.emptyFn,
116167                 click: Ext.emptyFn,
116168                 stopEvent: true
116169             });
116170         }
116171         return me.floatingButtons;
116172     },
116173
116174     reposition: function(animateConfig) {
116175         var me = this,
116176             context = me.context,
116177             row = context && Ext.get(context.row),
116178             btns = me.getFloatingButtons(),
116179             btnEl = btns.el,
116180             grid = me.editingPlugin.grid,
116181             viewEl = grid.view.el,
116182             scroller = grid.verticalScroller,
116183
116184             // always get data from ColumnModel as its what drives
116185             // the GridView's sizing
116186             mainBodyWidth = grid.headerCt.getFullWidth(),
116187             scrollerWidth = grid.getWidth(),
116188
116189             // use the minimum as the columns may not fill up the entire grid
116190             // width
116191             width = Math.min(mainBodyWidth, scrollerWidth),
116192             scrollLeft = grid.view.el.dom.scrollLeft,
116193             btnWidth = btns.getWidth(),
116194             left = (width - btnWidth) / 2 + scrollLeft,
116195             y, rowH, newHeight,
116196
116197             invalidateScroller = function() {
116198                 if (scroller) {
116199                     scroller.invalidate();
116200                     btnEl.scrollIntoView(viewEl, false);
116201                 }
116202                 if (animateConfig && animateConfig.callback) {
116203                     animateConfig.callback.call(animateConfig.scope || me);
116204                 }
116205             };
116206
116207         // need to set both top/left
116208         if (row && Ext.isElement(row.dom)) {
116209             // Bring our row into view if necessary, so a row editor that's already
116210             // visible and animated to the row will appear smooth
116211             row.scrollIntoView(viewEl, false);
116212
116213             // Get the y position of the row relative to its top-most static parent.
116214             // offsetTop will be relative to the table, and is incorrect
116215             // when mixed with certain grid features (e.g., grouping).
116216             y = row.getXY()[1] - 5;
116217             rowH = row.getHeight();
116218             newHeight = rowH + 10;
116219
116220             // IE doesn't set the height quite right.
116221             // This isn't a border-box issue, it even happens
116222             // in IE8 and IE7 quirks.
116223             // TODO: Test in IE9!
116224             if (Ext.isIE) {
116225                 newHeight += 2;
116226             }
116227
116228             // Set editor height to match the row height
116229             if (me.getHeight() != newHeight) {
116230                 me.setHeight(newHeight);
116231                 me.el.setLeft(0);
116232             }
116233
116234             if (animateConfig) {
116235                 var animObj = {
116236                     to: {
116237                         y: y
116238                     },
116239                     duration: animateConfig.duration || 125,
116240                     listeners: {
116241                         afteranimate: function() {
116242                             invalidateScroller();
116243                             y = row.getXY()[1] - 5;
116244                             me.el.setY(y);
116245                         }
116246                     }
116247                 };
116248                 me.animate(animObj);
116249             } else {
116250                 me.el.setY(y);
116251                 invalidateScroller();
116252             }
116253         }
116254         if (me.getWidth() != mainBodyWidth) {
116255             me.setWidth(mainBodyWidth);
116256         }
116257         btnEl.setLeft(left);
116258     },
116259
116260     getEditor: function(fieldInfo) {
116261         var me = this;
116262
116263         if (Ext.isNumber(fieldInfo)) {
116264             // Query only form fields. This just future-proofs us in case we add
116265             // other components to RowEditor later on.  Don't want to mess with
116266             // indices.
116267             return me.query('>[isFormField]')[fieldInfo];
116268         } else if (fieldInfo instanceof Ext.grid.column.Column) {
116269             return fieldInfo.getEditor();
116270         }
116271     },
116272
116273     removeField: function(field) {
116274         var me = this;
116275
116276         // Incase we pass a column instead, which is fine
116277         field = me.getEditor(field);
116278         me.mun(field, 'validitychange', me.onValidityChange, me);
116279
116280         // Remove field/column from our mapping, which will fire the event to
116281         // remove the field from our container
116282         me.columns.removeKey(field.id);
116283     },
116284
116285     setField: function(column) {
116286         var me = this,
116287             field;
116288
116289         if (Ext.isArray(column)) {
116290             Ext.Array.forEach(column, me.setField, me);
116291             return;
116292         }
116293
116294         // Get a default display field if necessary
116295         field = column.getEditor(null, {
116296             xtype: 'displayfield',
116297             // Default display fields will not return values. This is done because
116298             // the display field will pick up column renderers from the grid.
116299             getModelData: function() {
116300                 return null;
116301             }
116302         });
116303         field.margins = '0 0 0 2';
116304         field.setWidth(column.getDesiredWidth() - 2);
116305         me.mon(field, 'change', me.onFieldChange, me);
116306
116307         // Maintain mapping of fields-to-columns
116308         // This will fire events that maintain our container items
116309         me.columns.add(field.id, column);
116310         if (column.hidden) {
116311             me.onColumnHide(column);
116312         }
116313         if (me.isVisible() && me.context) {
116314             me.renderColumnData(field, me.context.record);
116315         }
116316     },
116317
116318     loadRecord: function(record) {
116319         var me = this,
116320             form = me.getForm();
116321         form.loadRecord(record);
116322         if (form.isValid()) {
116323             me.hideToolTip();
116324         } else {
116325             me.showToolTip();
116326         }
116327
116328         // render display fields so they honor the column renderer/template
116329         Ext.Array.forEach(me.query('>displayfield'), function(field) {
116330             me.renderColumnData(field, record);
116331         }, me);
116332     },
116333
116334     renderColumnData: function(field, record) {
116335         var me = this,
116336             grid = me.editingPlugin.grid,
116337             headerCt = grid.headerCt,
116338             view = grid.view,
116339             store = view.store,
116340             column = me.columns.get(field.id),
116341             value = record.get(column.dataIndex);
116342
116343         // honor our column's renderer (TemplateHeader sets renderer for us!)
116344         if (column.renderer) {
116345             var metaData = { tdCls: '', style: '' },
116346                 rowIdx = store.indexOf(record),
116347                 colIdx = headerCt.getHeaderIndex(column);
116348
116349             value = column.renderer.call(
116350                 column.scope || headerCt.ownerCt,
116351                 value,
116352                 metaData,
116353                 record,
116354                 rowIdx,
116355                 colIdx,
116356                 store,
116357                 view
116358             );
116359         }
116360
116361         field.setRawValue(value);
116362         field.resetOriginalValue();
116363     },
116364
116365     beforeEdit: function() {
116366         var me = this;
116367
116368         if (me.isVisible() && !me.autoCancel && me.isDirty()) {
116369             me.showToolTip();
116370             return false;
116371         }
116372     },
116373
116374     /**
116375      * Start editing the specified grid at the specified position.
116376      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
116377      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited.
116378      */
116379     startEdit: function(record, columnHeader) {
116380         var me = this,
116381             grid = me.editingPlugin.grid,
116382             view = grid.getView(),
116383             store = grid.store,
116384             context = me.context = Ext.apply(me.editingPlugin.context, {
116385                 view: grid.getView(),
116386                 store: store
116387             });
116388
116389         // make sure our row is selected before editing
116390         context.grid.getSelectionModel().select(record);
116391
116392         // Reload the record data
116393         me.loadRecord(record);
116394
116395         if (!me.isVisible()) {
116396             me.show();
116397             me.focusContextCell();
116398         } else {
116399             me.reposition({
116400                 callback: this.focusContextCell
116401             });
116402         }
116403     },
116404
116405     // Focus the cell on start edit based upon the current context
116406     focusContextCell: function() {
116407         var field = this.getEditor(this.context.colIdx);
116408         if (field && field.focus) {
116409             field.focus();
116410         }
116411     },
116412
116413     cancelEdit: function() {
116414         var me = this,
116415             form = me.getForm();
116416
116417         me.hide();
116418         form.clearInvalid();
116419         form.reset();
116420     },
116421
116422     completeEdit: function() {
116423         var me = this,
116424             form = me.getForm();
116425
116426         if (!form.isValid()) {
116427             return;
116428         }
116429
116430         form.updateRecord(me.context.record);
116431         me.hide();
116432         return true;
116433     },
116434
116435     onShow: function() {
116436         var me = this;
116437         me.callParent(arguments);
116438         me.reposition();
116439     },
116440
116441     onHide: function() {
116442         var me = this;
116443         me.callParent(arguments);
116444         me.hideToolTip();
116445         me.invalidateScroller();
116446         if (me.context) {
116447             me.context.view.focus();
116448             me.context = null;
116449         }
116450     },
116451
116452     isDirty: function() {
116453         var me = this,
116454             form = me.getForm();
116455         return form.isDirty();
116456     },
116457
116458     getToolTip: function() {
116459         var me = this,
116460             tip;
116461
116462         if (!me.tooltip) {
116463             tip = me.tooltip = Ext.createWidget('tooltip', {
116464                 cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
116465                 title: me.errorsText,
116466                 autoHide: false,
116467                 closable: true,
116468                 closeAction: 'disable',
116469                 anchor: 'left'
116470             });
116471         }
116472         return me.tooltip;
116473     },
116474
116475     hideToolTip: function() {
116476         var me = this,
116477             tip = me.getToolTip();
116478         if (tip.rendered) {
116479             tip.disable();
116480         }
116481         me.hiddenTip = false;
116482     },
116483
116484     showToolTip: function() {
116485         var me = this,
116486             tip = me.getToolTip(),
116487             context = me.context,
116488             row = Ext.get(context.row),
116489             viewEl = context.grid.view.el;
116490
116491         tip.setTarget(row);
116492         tip.showAt([-10000, -10000]);
116493         tip.body.update(me.getErrors());
116494         tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
116495         me.repositionTip();
116496         tip.doLayout();
116497         tip.enable();
116498     },
116499
116500     repositionTip: function() {
116501         var me = this,
116502             tip = me.getToolTip(),
116503             context = me.context,
116504             row = Ext.get(context.row),
116505             viewEl = context.grid.view.el,
116506             viewHeight = viewEl.getHeight(),
116507             viewTop = me.lastScrollTop,
116508             viewBottom = viewTop + viewHeight,
116509             rowHeight = row.getHeight(),
116510             rowTop = row.dom.offsetTop,
116511             rowBottom = rowTop + rowHeight;
116512
116513         if (rowBottom > viewTop && rowTop < viewBottom) {
116514             tip.show();
116515             me.hiddenTip = false;
116516         } else {
116517             tip.hide();
116518             me.hiddenTip = true;
116519         }
116520     },
116521
116522     getErrors: function() {
116523         var me = this,
116524             dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',
116525             errors = [];
116526
116527         Ext.Array.forEach(me.query('>[isFormField]'), function(field) {
116528             errors = errors.concat(
116529                 Ext.Array.map(field.getErrors(), function(e) {
116530                     return '<li>' + e + '</li>';
116531                 })
116532             );
116533         }, me);
116534
116535         return dirtyText + '<ul>' + errors.join('') + '</ul>';
116536     },
116537
116538     invalidateScroller: function() {
116539         var me = this,
116540             context = me.context,
116541             scroller = context.grid.verticalScroller;
116542
116543         if (scroller) {
116544             scroller.invalidate();
116545         }
116546     }
116547 });
116548 /**
116549  * @class Ext.grid.header.Container
116550  * @extends Ext.container.Container
116551  *
116552  * Container which holds headers and is docked at the top or bottom of a TablePanel.
116553  * The HeaderContainer drives resizing/moving/hiding of columns within the TableView.
116554  * As headers are hidden, moved or resized the headercontainer is responsible for
116555  * triggering changes within the view.
116556  */
116557 Ext.define('Ext.grid.header.Container', {
116558     extend: 'Ext.container.Container',
116559     uses: [
116560         'Ext.grid.ColumnLayout',
116561         'Ext.grid.column.Column',
116562         'Ext.menu.Menu',
116563         'Ext.menu.CheckItem',
116564         'Ext.menu.Separator',
116565         'Ext.grid.plugin.HeaderResizer',
116566         'Ext.grid.plugin.HeaderReorderer'
116567     ],
116568     border: true,
116569
116570     alias: 'widget.headercontainer',
116571
116572     baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
116573     dock: 'top',
116574
116575     /**
116576      * @cfg {Number} weight
116577      * HeaderContainer overrides the default weight of 0 for all docked items to 100.
116578      * This is so that it has more priority over things like toolbars.
116579      */
116580     weight: 100,
116581     defaultType: 'gridcolumn',
116582     /**
116583      * @cfg {Number} defaultWidth
116584      * Width of the header if no width or flex is specified. Defaults to 100.
116585      */
116586     defaultWidth: 100,
116587
116588
116589     sortAscText: 'Sort Ascending',
116590     sortDescText: 'Sort Descending',
116591     sortClearText: 'Clear Sort',
116592     columnsText: 'Columns',
116593
116594     lastHeaderCls: Ext.baseCSSPrefix + 'column-header-last',
116595     firstHeaderCls: Ext.baseCSSPrefix + 'column-header-first',
116596     headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
116597
116598     // private; will probably be removed by 4.0
116599     triStateSort: false,
116600
116601     ddLock: false,
116602
116603     dragging: false,
116604
116605     /**
116606      * <code>true</code> if this HeaderContainer is in fact a group header which contains sub headers.
116607      * @type Boolean
116608      * @property isGroupHeader
116609      */
116610
116611     /**
116612      * @cfg {Boolean} sortable
116613      * Provides the default sortable state for all Headers within this HeaderContainer.
116614      * Also turns on or off the menus in the HeaderContainer. Note that the menu is
116615      * shared across every header and therefore turning it off will remove the menu
116616      * items for every header.
116617      */
116618     sortable: true,
116619
116620     initComponent: function() {
116621         var me = this;
116622
116623         me.headerCounter = 0;
116624         me.plugins = me.plugins || [];
116625
116626         // TODO: Pass in configurations to turn on/off dynamic
116627         //       resizing and disable resizing all together
116628
116629         // Only set up a Resizer and Reorderer for the topmost HeaderContainer.
116630         // Nested Group Headers are themselves HeaderContainers
116631         if (!me.isHeader) {
116632             me.resizer   = Ext.create('Ext.grid.plugin.HeaderResizer');
116633             me.reorderer = Ext.create('Ext.grid.plugin.HeaderReorderer');
116634             if (!me.enableColumnResize) {
116635                 me.resizer.disable();
116636             }
116637             if (!me.enableColumnMove) {
116638                 me.reorderer.disable();
116639             }
116640             me.plugins.push(me.reorderer, me.resizer);
116641         }
116642
116643         // Base headers do not need a box layout
116644         if (me.isHeader && !me.items) {
116645             me.layout = 'auto';
116646         }
116647         // HeaderContainer and Group header needs a gridcolumn layout.
116648         else {
116649             me.layout = {
116650                 type: 'gridcolumn',
116651                 availableSpaceOffset: me.availableSpaceOffset,
116652                 align: 'stretchmax',
116653                 resetStretch: true
116654             };
116655         }
116656         me.defaults = me.defaults || {};
116657         Ext.applyIf(me.defaults, {
116658             width: me.defaultWidth,
116659             triStateSort: me.triStateSort,
116660             sortable: me.sortable
116661         });
116662         me.callParent();
116663         me.addEvents(
116664             /**
116665              * @event columnresize
116666              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116667              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116668              * @param {Number} width
116669              */
116670             'columnresize',
116671
116672             /**
116673              * @event headerclick
116674              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116675              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116676              * @param {Ext.EventObject} e
116677              * @param {HTMLElement} t
116678              */
116679             'headerclick',
116680
116681             /**
116682              * @event headertriggerclick
116683              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116684              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116685              * @param {Ext.EventObject} e
116686              * @param {HTMLElement} t
116687              */
116688             'headertriggerclick',
116689
116690             /**
116691              * @event columnmove
116692              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116693              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116694              * @param {Number} fromIdx
116695              * @param {Number} toIdx
116696              */
116697             'columnmove',
116698             /**
116699              * @event columnhide
116700              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116701              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116702              */
116703             'columnhide',
116704             /**
116705              * @event columnshow
116706              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116707              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116708              */
116709             'columnshow',
116710             /**
116711              * @event sortchange
116712              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116713              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116714              * @param {String} direction
116715              */
116716             'sortchange',
116717             /**
116718              * @event menucreate
116719              * Fired immediately after the column header menu is created.
116720              * @param {Ext.grid.header.Container} ct This instance
116721              * @param {Ext.menu.Menu} menu The Menu that was created
116722              */
116723             'menucreate'
116724         );
116725     },
116726
116727     onDestroy: function() {
116728         Ext.destroy(this.resizer, this.reorderer);
116729         this.callParent();
116730     },
116731     
116732     applyDefaults: function(config){
116733         /*
116734          * Ensure header.Container defaults don't get applied to a RowNumberer 
116735          * if an xtype is supplied. This isn't an ideal solution however it's 
116736          * much more likely that a RowNumberer with no options will be created, 
116737          * wanting to use the defaults specified on the class as opposed to 
116738          * those setup on the Container.
116739          */
116740         if (config && !config.isComponent && config.xtype == 'rownumberer') {
116741             return config;
116742         }
116743         return this.callParent([config]);
116744     },
116745
116746     applyColumnsState: function(columns) {
116747         if (!columns || !columns.length) {
116748             return;
116749         }
116750
116751         var me = this,
116752             i = 0,
116753             index,
116754             col;
116755
116756         Ext.each(columns, function (columnState) {
116757             col = me.down('gridcolumn[headerId=' + columnState.id + ']');
116758             if (col) {
116759                 index = me.items.indexOf(col);
116760                 if (i !== index) {
116761                     me.moveHeader(index, i);
116762                 }
116763
116764                 if (col.applyColumnState) {
116765                     col.applyColumnState(columnState);
116766                 }
116767                 ++i;
116768             }
116769         });
116770     },
116771
116772     getColumnsState: function () {
116773         var me = this,
116774             columns = [],
116775             state;
116776
116777         me.items.each(function (col) {
116778             state = col.getColumnState && col.getColumnState();
116779             if (state) {
116780                 columns.push(state);
116781             }
116782         });
116783
116784         return columns;
116785     },
116786
116787     // Invalidate column cache on add
116788     // We cannot refresh the View on every add because this method is called
116789     // when the HeaderDropZone moves Headers around, that will also refresh the view
116790     onAdd: function(c) {
116791         var me = this;
116792         if (!c.headerId) {
116793             c.headerId = c.initialConfig.id || ('h' + (++me.headerCounter));
116794         }
116795         if (Ext.global.console && Ext.global.console.warn) {
116796             if (!me._usedIDs) me._usedIDs = {};
116797             if (me._usedIDs[c.headerId]) {
116798                 Ext.global.console.warn(this.$className, 'attempted to reuse an existing id', c.headerId);
116799             }
116800             me._usedIDs[c.headerId] = true;
116801         }
116802         me.callParent(arguments);
116803         me.purgeCache();
116804     },
116805
116806     // Invalidate column cache on remove
116807     // We cannot refresh the View on every remove because this method is called
116808     // when the HeaderDropZone moves Headers around, that will also refresh the view
116809     onRemove: function(c) {
116810         var me = this;
116811         me.callParent(arguments);
116812         me.purgeCache();
116813     },
116814
116815     afterRender: function() {
116816         this.callParent();
116817         var store   = this.up('[store]').store,
116818             sorters = store.sorters,
116819             first   = sorters.first(),
116820             hd;
116821
116822         if (first) {
116823             hd = this.down('gridcolumn[dataIndex=' + first.property  +']');
116824             if (hd) {
116825                 hd.setSortState(first.direction, false, true);
116826             }
116827         }
116828     },
116829
116830     afterLayout: function() {
116831         if (!this.isHeader) {
116832             var me = this,
116833                 topHeaders = me.query('>gridcolumn:not([hidden])'),
116834                 viewEl,
116835                 firstHeaderEl,
116836                 lastHeaderEl;
116837
116838             me.callParent(arguments);
116839
116840             if (topHeaders.length) {
116841                 firstHeaderEl = topHeaders[0].el;
116842                 if (firstHeaderEl !== me.pastFirstHeaderEl) {
116843                     if (me.pastFirstHeaderEl) {
116844                         me.pastFirstHeaderEl.removeCls(me.firstHeaderCls);
116845                     }
116846                     firstHeaderEl.addCls(me.firstHeaderCls);
116847                     me.pastFirstHeaderEl = firstHeaderEl;
116848                 }
116849
116850                 lastHeaderEl = topHeaders[topHeaders.length - 1].el;
116851                 if (lastHeaderEl !== me.pastLastHeaderEl) {
116852                     if (me.pastLastHeaderEl) {
116853                         me.pastLastHeaderEl.removeCls(me.lastHeaderCls);
116854                     }
116855                     lastHeaderEl.addCls(me.lastHeaderCls);
116856                     me.pastLastHeaderEl = lastHeaderEl;
116857                 }
116858             }
116859         }
116860
116861     },
116862
116863     onHeaderShow: function(header, preventLayout) {
116864         // Pass up to the GridSection
116865         var me = this,
116866             gridSection = me.ownerCt,
116867             menu = me.getMenu(),
116868             topItems, topItemsVisible,
116869             colCheckItem,
116870             itemToEnable,
116871             len, i;
116872
116873         if (menu) {
116874
116875             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
116876             if (colCheckItem) {
116877                 colCheckItem.setChecked(true, true);
116878             }
116879
116880             // There's more than one header visible, and we've disabled some checked items... re-enable them
116881             topItems = menu.query('#columnItem>menucheckitem[checked]');
116882             topItemsVisible = topItems.length;
116883             if ((me.getVisibleGridColumns().length > 1) && me.disabledMenuItems && me.disabledMenuItems.length) {
116884                 if (topItemsVisible == 1) {
116885                     Ext.Array.remove(me.disabledMenuItems, topItems[0]);
116886                 }
116887                 for (i = 0, len = me.disabledMenuItems.length; i < len; i++) {
116888                     itemToEnable = me.disabledMenuItems[i];
116889                     if (!itemToEnable.isDestroyed) {
116890                         itemToEnable[itemToEnable.menu ? 'enableCheckChange' : 'enable']();
116891                     }
116892                 }
116893                 if (topItemsVisible == 1) {
116894                     me.disabledMenuItems = topItems;
116895                 } else {
116896                     me.disabledMenuItems = [];
116897                 }
116898             }
116899         }
116900
116901         // Only update the grid UI when we are notified about base level Header shows;
116902         // Group header shows just cause a layout of the HeaderContainer
116903         if (!header.isGroupHeader) {
116904             if (me.view) {
116905                 me.view.onHeaderShow(me, header, true);
116906             }
116907             if (gridSection) {
116908                 gridSection.onHeaderShow(me, header);
116909             }
116910         }
116911         me.fireEvent('columnshow', me, header);
116912
116913         // The header's own hide suppresses cascading layouts, so lay the headers out now
116914         if (preventLayout !== true) {
116915             me.doLayout();
116916         }
116917     },
116918
116919     doComponentLayout: function(){
116920         var me = this;
116921         if (me.view && me.view.saveScrollState) {
116922             me.view.saveScrollState();
116923         }
116924         me.callParent(arguments);
116925         if (me.view && me.view.restoreScrollState) {
116926             me.view.restoreScrollState();
116927         }
116928     },
116929
116930     onHeaderHide: function(header, suppressLayout) {
116931         // Pass up to the GridSection
116932         var me = this,
116933             gridSection = me.ownerCt,
116934             menu = me.getMenu(),
116935             colCheckItem;
116936
116937         if (menu) {
116938
116939             // If the header was hidden programmatically, sync the Menu state
116940             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
116941             if (colCheckItem) {
116942                 colCheckItem.setChecked(false, true);
116943             }
116944             me.setDisabledItems();
116945         }
116946
116947         // Only update the UI when we are notified about base level Header hides;
116948         if (!header.isGroupHeader) {
116949             if (me.view) {
116950                 me.view.onHeaderHide(me, header, true);
116951             }
116952             if (gridSection) {
116953                 gridSection.onHeaderHide(me, header);
116954             }
116955
116956             // The header's own hide suppresses cascading layouts, so lay the headers out now
116957             if (!suppressLayout) {
116958                 me.doLayout();
116959             }
116960         }
116961         me.fireEvent('columnhide', me, header);
116962     },
116963
116964     setDisabledItems: function(){
116965         var me = this,
116966             menu = me.getMenu(),
116967             i = 0,
116968             len,
116969             itemsToDisable,
116970             itemToDisable;
116971
116972         // Find what to disable. If only one top level item remaining checked, we have to disable stuff.
116973         itemsToDisable = menu.query('#columnItem>menucheckitem[checked]');
116974         if ((itemsToDisable.length === 1)) {
116975             if (!me.disabledMenuItems) {
116976                 me.disabledMenuItems = [];
116977             }
116978
116979             // If down to only one column visible, also disable any descendant checkitems
116980             if ((me.getVisibleGridColumns().length === 1) && itemsToDisable[0].menu) {
116981                 itemsToDisable = itemsToDisable.concat(itemsToDisable[0].menu.query('menucheckitem[checked]'));
116982             }
116983
116984             len = itemsToDisable.length;
116985             // Disable any further unchecking at any level.
116986             for (i = 0; i < len; i++) {
116987                 itemToDisable = itemsToDisable[i];
116988                 if (!Ext.Array.contains(me.disabledMenuItems, itemToDisable)) {
116989
116990                     // If we only want to disable check change: it might be a disabled item, so enable it prior to
116991                     // setting its correct disablement level.
116992                     itemToDisable.disabled = false;
116993                     itemToDisable[itemToDisable.menu ? 'disableCheckChange' : 'disable']();
116994                     me.disabledMenuItems.push(itemToDisable);
116995                 }
116996             }
116997         }
116998     },
116999
117000     /**
117001      * Temporarily lock the headerCt. This makes it so that clicking on headers
117002      * don't trigger actions like sorting or opening of the header menu. This is
117003      * done because extraneous events may be fired on the headers after interacting
117004      * with a drag drop operation.
117005      * @private
117006      */
117007     tempLock: function() {
117008         this.ddLock = true;
117009         Ext.Function.defer(function() {
117010             this.ddLock = false;
117011         }, 200, this);
117012     },
117013
117014     onHeaderResize: function(header, w, suppressFocus) {
117015         this.tempLock();
117016         if (this.view && this.view.rendered) {
117017             this.view.onHeaderResize(header, w, suppressFocus);
117018         }
117019     },
117020
117021     onHeaderClick: function(header, e, t) {
117022         this.fireEvent("headerclick", this, header, e, t);
117023     },
117024
117025     onHeaderTriggerClick: function(header, e, t) {
117026         // generate and cache menu, provide ability to cancel/etc
117027         if (this.fireEvent("headertriggerclick", this, header, e, t) !== false) {
117028             this.showMenuBy(t, header);
117029         }
117030     },
117031
117032     showMenuBy: function(t, header) {
117033         var menu = this.getMenu(),
117034             ascItem  = menu.down('#ascItem'),
117035             descItem = menu.down('#descItem'),
117036             sortableMth;
117037
117038         menu.activeHeader = menu.ownerCt = header;
117039         menu.setFloatParent(header);
117040         // TODO: remove coupling to Header's titleContainer el
117041         header.titleContainer.addCls(this.headerOpenCls);
117042
117043         // enable or disable asc & desc menu items based on header being sortable
117044         sortableMth = header.sortable ? 'enable' : 'disable';
117045         if (ascItem) {
117046             ascItem[sortableMth]();
117047         }
117048         if (descItem) {
117049             descItem[sortableMth]();
117050         }
117051         menu.showBy(t);
117052     },
117053
117054     // remove the trigger open class when the menu is hidden
117055     onMenuDeactivate: function() {
117056         var menu = this.getMenu();
117057         // TODO: remove coupling to Header's titleContainer el
117058         menu.activeHeader.titleContainer.removeCls(this.headerOpenCls);
117059     },
117060
117061     moveHeader: function(fromIdx, toIdx) {
117062
117063         // An automatically expiring lock
117064         this.tempLock();
117065         this.onHeaderMoved(this.move(fromIdx, toIdx), fromIdx, toIdx);
117066     },
117067
117068     purgeCache: function() {
117069         var me = this;
117070         // Delete column cache - column order has changed.
117071         delete me.gridDataColumns;
117072         delete me.hideableColumns;
117073
117074         // Menu changes when columns are moved. It will be recreated.
117075         if (me.menu) {
117076             me.menu.destroy();
117077             delete me.menu;
117078         }
117079     },
117080
117081     onHeaderMoved: function(header, fromIdx, toIdx) {
117082         var me = this,
117083             gridSection = me.ownerCt;
117084
117085         if (gridSection && gridSection.onHeaderMove) {
117086             gridSection.onHeaderMove(me, header, fromIdx, toIdx);
117087         }
117088         me.fireEvent("columnmove", me, header, fromIdx, toIdx);
117089     },
117090
117091     /**
117092      * Gets the menu (and will create it if it doesn't already exist)
117093      * @private
117094      */
117095     getMenu: function() {
117096         var me = this;
117097
117098         if (!me.menu) {
117099             me.menu = Ext.create('Ext.menu.Menu', {
117100                 hideOnParentHide: false,  // Persists when owning ColumnHeader is hidden
117101                 items: me.getMenuItems(),
117102                 listeners: {
117103                     deactivate: me.onMenuDeactivate,
117104                     scope: me
117105                 }
117106             });
117107             me.setDisabledItems();
117108             me.fireEvent('menucreate', me, me.menu);
117109         }
117110         return me.menu;
117111     },
117112
117113     /**
117114      * Returns an array of menu items to be placed into the shared menu
117115      * across all headers in this header container.
117116      * @returns {Array} menuItems
117117      */
117118     getMenuItems: function() {
117119         var me = this,
117120             menuItems = [],
117121             hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
117122
117123         if (me.sortable) {
117124             menuItems = [{
117125                 itemId: 'ascItem',
117126                 text: me.sortAscText,
117127                 cls: Ext.baseCSSPrefix + 'hmenu-sort-asc',
117128                 handler: me.onSortAscClick,
117129                 scope: me
117130             },{
117131                 itemId: 'descItem',
117132                 text: me.sortDescText,
117133                 cls: Ext.baseCSSPrefix + 'hmenu-sort-desc',
117134                 handler: me.onSortDescClick,
117135                 scope: me
117136             }];
117137         }
117138         if (hideableColumns && hideableColumns.length) {
117139             menuItems.push('-', {
117140                 itemId: 'columnItem',
117141                 text: me.columnsText,
117142                 cls: Ext.baseCSSPrefix + 'cols-icon',
117143                 menu: hideableColumns
117144             });
117145         }
117146         return menuItems;
117147     },
117148
117149     // sort asc when clicking on item in menu
117150     onSortAscClick: function() {
117151         var menu = this.getMenu(),
117152             activeHeader = menu.activeHeader;
117153
117154         activeHeader.setSortState('ASC');
117155     },
117156
117157     // sort desc when clicking on item in menu
117158     onSortDescClick: function() {
117159         var menu = this.getMenu(),
117160             activeHeader = menu.activeHeader;
117161
117162         activeHeader.setSortState('DESC');
117163     },
117164
117165     /**
117166      * Returns an array of menu CheckItems corresponding to all immediate children of the passed Container which have been configured as hideable.
117167      */
117168     getColumnMenu: function(headerContainer) {
117169         var menuItems = [],
117170             i = 0,
117171             item,
117172             items = headerContainer.query('>gridcolumn[hideable]'),
117173             itemsLn = items.length,
117174             menuItem;
117175
117176         for (; i < itemsLn; i++) {
117177             item = items[i];
117178             menuItem = Ext.create('Ext.menu.CheckItem', {
117179                 text: item.text,
117180                 checked: !item.hidden,
117181                 hideOnClick: false,
117182                 headerId: item.id,
117183                 menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
117184                 checkHandler: this.onColumnCheckChange,
117185                 scope: this
117186             });
117187             if (itemsLn === 1) {
117188                 menuItem.disabled = true;
117189             }
117190             menuItems.push(menuItem);
117191
117192             // If the header is ever destroyed - for instance by dragging out the last remaining sub header,
117193             // then the associated menu item must also be destroyed.
117194             item.on({
117195                 destroy: Ext.Function.bind(menuItem.destroy, menuItem)
117196             });
117197         }
117198         return menuItems;
117199     },
117200
117201     onColumnCheckChange: function(checkItem, checked) {
117202         var header = Ext.getCmp(checkItem.headerId);
117203         header[checked ? 'show' : 'hide']();
117204     },
117205
117206     /**
117207      * Get the columns used for generating a template via TableChunker.
117208      * Returns an array of all columns and their
117209      *  - dataIndex
117210      *  - align
117211      *  - width
117212      *  - id
117213      *  - columnId - used to create an identifying CSS class
117214      *  - cls The tdCls configuration from the Column object
117215      *  @private
117216      */
117217     getColumnsForTpl: function(flushCache) {
117218         var cols    = [],
117219             headers   = this.getGridColumns(flushCache),
117220             headersLn = headers.length,
117221             i = 0,
117222             header,
117223             width;
117224
117225         for (; i < headersLn; i++) {
117226             header = headers[i];
117227
117228             if (header.hidden || header.up('headercontainer[hidden=true]')) {
117229                 width = 0;
117230             } else {
117231                 width = header.getDesiredWidth();
117232                 // IE6 and IE7 bug.
117233                 // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
117234                 // We need to increment the passed with in this case.
117235                 if ((i === 0) && (Ext.isIE6 || Ext.isIE7)) {
117236                     width += 1;
117237                 }
117238             }
117239             cols.push({
117240                 dataIndex: header.dataIndex,
117241                 align: header.align,
117242                 width: width,
117243                 id: header.id,
117244                 cls: header.tdCls,
117245                 columnId: header.getItemId()
117246             });
117247         }
117248         return cols;
117249     },
117250
117251     /**
117252      * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
117253      * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
117254      */
117255     getColumnCount: function() {
117256         return this.getGridColumns().length;
117257     },
117258
117259     /**
117260      * Gets the full width of all columns that are visible.
117261      */
117262     getFullWidth: function(flushCache) {
117263         var fullWidth = 0,
117264             headers     = this.getVisibleGridColumns(flushCache),
117265             headersLn   = headers.length,
117266             i         = 0;
117267
117268         for (; i < headersLn; i++) {
117269             if (!isNaN(headers[i].width)) {
117270                 // use headers getDesiredWidth if its there
117271                 if (headers[i].getDesiredWidth) {
117272                     fullWidth += headers[i].getDesiredWidth();
117273                 // if injected a diff cmp use getWidth
117274                 } else {
117275                     fullWidth += headers[i].getWidth();
117276                 }
117277             }
117278         }
117279         return fullWidth;
117280     },
117281
117282     // invoked internally by a header when not using triStateSorting
117283     clearOtherSortStates: function(activeHeader) {
117284         var headers   = this.getGridColumns(),
117285             headersLn = headers.length,
117286             i         = 0,
117287             oldSortState;
117288
117289         for (; i < headersLn; i++) {
117290             if (headers[i] !== activeHeader) {
117291                 oldSortState = headers[i].sortState;
117292                 // unset the sortstate and dont recurse
117293                 headers[i].setSortState(null, true);
117294                 //if (!silent && oldSortState !== null) {
117295                 //    this.fireEvent('sortchange', this, headers[i], null);
117296                 //}
117297             }
117298         }
117299     },
117300
117301     /**
117302      * Returns an array of the <b>visible</b> columns in the grid. This goes down to the lowest column header
117303      * level, and does not return <i>grouped</i> headers which contain sub headers.
117304      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
117305      * @returns {Array}
117306      */
117307     getVisibleGridColumns: function(refreshCache) {
117308         return Ext.ComponentQuery.query(':not([hidden])', this.getGridColumns(refreshCache));
117309     },
117310
117311     /**
117312      * Returns an array of all columns which map to Store fields. This goes down to the lowest column header
117313      * level, and does not return <i>grouped</i> headers which contain sub headers.
117314      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
117315      * @returns {Array}
117316      */
117317     getGridColumns: function(refreshCache) {
117318         var me = this,
117319             result = refreshCache ? null : me.gridDataColumns;
117320
117321         // Not already got the column cache, so collect the base columns
117322         if (!result) {
117323             me.gridDataColumns = result = [];
117324             me.cascade(function(c) {
117325                 if ((c !== me) && !c.isGroupHeader) {
117326                     result.push(c);
117327                 }
117328             });
117329         }
117330
117331         return result;
117332     },
117333
117334     /**
117335      * @private
117336      * For use by column headers in determining whether there are any hideable columns when deciding whether or not
117337      * the header menu should be disabled.
117338      */
117339     getHideableColumns: function(refreshCache) {
117340         var me = this,
117341             result = refreshCache ? null : me.hideableColumns;
117342
117343         if (!result) {
117344             result = me.hideableColumns = me.query('[hideable]');
117345         }
117346         return result;
117347     },
117348
117349     /**
117350      * Get the index of a leaf level header regardless of what the nesting
117351      * structure is.
117352      */
117353     getHeaderIndex: function(header) {
117354         var columns = this.getGridColumns();
117355         return Ext.Array.indexOf(columns, header);
117356     },
117357
117358     /**
117359      * Get a leaf level header by index regardless of what the nesting
117360      * structure is.
117361      */
117362     getHeaderAtIndex: function(index) {
117363         var columns = this.getGridColumns();
117364         return columns[index];
117365     },
117366
117367     /**
117368      * Maps the record data to base it on the header id's.
117369      * This correlates to the markup/template generated by
117370      * TableChunker.
117371      */
117372     prepareData: function(data, rowIdx, record, view, panel) {
117373         var obj       = {},
117374             headers   = this.gridDataColumns || this.getGridColumns(),
117375             headersLn = headers.length,
117376             colIdx    = 0,
117377             header,
117378             headerId,
117379             renderer,
117380             value,
117381             metaData,
117382             store = panel.store;
117383
117384         for (; colIdx < headersLn; colIdx++) {
117385             metaData = {
117386                 tdCls: '',
117387                 style: ''
117388             };
117389             header = headers[colIdx];
117390             headerId = header.id;
117391             renderer = header.renderer;
117392             value = data[header.dataIndex];
117393
117394             // When specifying a renderer as a string, it always resolves
117395             // to Ext.util.Format
117396             if (typeof renderer === "string") {
117397                 header.renderer = renderer = Ext.util.Format[renderer];
117398             }
117399
117400             if (typeof renderer === "function") {
117401                 value = renderer.call(
117402                     header.scope || this.ownerCt,
117403                     value,
117404                     // metadata per cell passing an obj by reference so that
117405                     // it can be manipulated inside the renderer
117406                     metaData,
117407                     record,
117408                     rowIdx,
117409                     colIdx,
117410                     store,
117411                     view
117412                 );
117413             }
117414
117415             if (metaData.css) {
117416                 // This warning attribute is used by the compat layer
117417                 obj.cssWarning = true;
117418                 metaData.tdCls = metaData.css;
117419                 delete metaData.css;
117420             }
117421
117422             obj[headerId+'-modified'] = record.isModified(header.dataIndex) ? Ext.baseCSSPrefix + 'grid-dirty-cell' : '';
117423             obj[headerId+'-tdCls'] = metaData.tdCls;
117424             obj[headerId+'-tdAttr'] = metaData.tdAttr;
117425             obj[headerId+'-style'] = metaData.style;
117426             if (value === undefined || value === null || value === '') {
117427                 value = '&#160;';
117428             }
117429             obj[headerId] = value;
117430         }
117431         return obj;
117432     },
117433
117434     expandToFit: function(header) {
117435         if (this.view) {
117436             this.view.expandToFit(header);
117437         }
117438     }
117439 });
117440
117441 /**
117442  * This class specifies the definition for a column inside a {@link Ext.grid.Panel}. It encompasses
117443  * both the grid header configuration as well as displaying data within the grid itself. If the
117444  * {@link #columns} configuration is specified, this column will become a column group and can
117445  * contain other columns inside. In general, this class will not be created directly, rather
117446  * an array of column configurations will be passed to the grid:
117447  *
117448  *     @example
117449  *     Ext.create('Ext.data.Store', {
117450  *         storeId:'employeeStore',
117451  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
117452  *         data:[
117453  *             {firstname:"Michael", lastname:"Scott", senority:7, dep:"Manangement", hired:"01/10/2004"},
117454  *             {firstname:"Dwight", lastname:"Schrute", senority:2, dep:"Sales", hired:"04/01/2004"},
117455  *             {firstname:"Jim", lastname:"Halpert", senority:3, dep:"Sales", hired:"02/22/2006"},
117456  *             {firstname:"Kevin", lastname:"Malone", senority:4, dep:"Accounting", hired:"06/10/2007"},
117457  *             {firstname:"Angela", lastname:"Martin", senority:5, dep:"Accounting", hired:"10/21/2008"}
117458  *         ]
117459  *     });
117460  *
117461  *     Ext.create('Ext.grid.Panel', {
117462  *         title: 'Column Demo',
117463  *         store: Ext.data.StoreManager.lookup('employeeStore'),
117464  *         columns: [
117465  *             {text: 'First Name',  dataIndex:'firstname'},
117466  *             {text: 'Last Name',  dataIndex:'lastname'},
117467  *             {text: 'Hired Month',  dataIndex:'hired', xtype:'datecolumn', format:'M'},
117468  *             {text: 'Department (Yrs)', xtype:'templatecolumn', tpl:'{dep} ({senority})'}
117469  *         ],
117470  *         width: 400,
117471  *         renderTo: Ext.getBody()
117472  *     });
117473  *
117474  * # Convenience Subclasses
117475  *
117476  * There are several column subclasses that provide default rendering for various data types
117477  *
117478  *  - {@link Ext.grid.column.Action}: Renders icons that can respond to click events inline
117479  *  - {@link Ext.grid.column.Boolean}: Renders for boolean values
117480  *  - {@link Ext.grid.column.Date}: Renders for date values
117481  *  - {@link Ext.grid.column.Number}: Renders for numeric values
117482  *  - {@link Ext.grid.column.Template}: Renders a value using an {@link Ext.XTemplate} using the record data
117483  *
117484  * # Setting Sizes
117485  *
117486  * The columns are laid out by a {@link Ext.layout.container.HBox} layout, so a column can either
117487  * be given an explicit width value or a flex configuration. If no width is specified the grid will
117488  * automatically the size the column to 100px. For column groups, the size is calculated by measuring
117489  * the width of the child columns, so a width option should not be specified in that case.
117490  *
117491  * # Header Options
117492  *
117493  *  - {@link #text}: Sets the header text for the column
117494  *  - {@link #sortable}: Specifies whether the column can be sorted by clicking the header or using the column menu
117495  *  - {@link #hideable}: Specifies whether the column can be hidden using the column menu
117496  *  - {@link #menuDisabled}: Disables the column header menu
117497  *  - {@link #draggable}: Specifies whether the column header can be reordered by dragging
117498  *  - {@link #groupable}: Specifies whether the grid can be grouped by the column dataIndex. See also {@link Ext.grid.feature.Grouping}
117499  *
117500  * # Data Options
117501  *
117502  *  - {@link #dataIndex}: The dataIndex is the field in the underlying {@link Ext.data.Store} to use as the value for the column.
117503  *  - {@link #renderer}: Allows the underlying store value to be transformed before being displayed in the grid
117504  */
117505 Ext.define('Ext.grid.column.Column', {
117506     extend: 'Ext.grid.header.Container',
117507     alias: 'widget.gridcolumn',
117508     requires: ['Ext.util.KeyNav'],
117509     alternateClassName: 'Ext.grid.Column',
117510
117511     baseCls: Ext.baseCSSPrefix + 'column-header ' + Ext.baseCSSPrefix + 'unselectable',
117512
117513     // Not the standard, automatically applied overCls because we must filter out overs of child headers.
117514     hoverCls: Ext.baseCSSPrefix + 'column-header-over',
117515
117516     handleWidth: 5,
117517
117518     sortState: null,
117519
117520     possibleSortStates: ['ASC', 'DESC'],
117521
117522     renderTpl:
117523         '<div id="{id}-titleContainer" class="' + Ext.baseCSSPrefix + 'column-header-inner">' +
117524             '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'column-header-text">' +
117525                 '{text}' +
117526             '</span>' +
117527             '<tpl if="!values.menuDisabled">'+
117528                 '<div id="{id}-triggerEl" class="' + Ext.baseCSSPrefix + 'column-header-trigger"></div>'+
117529             '</tpl>' +
117530         '</div>',
117531
117532     /**
117533      * @cfg {Object[]} columns
117534      * An optional array of sub-column definitions. This column becomes a group, and houses the columns defined in the
117535      * `columns` config.
117536      *
117537      * Group columns may not be sortable. But they may be hideable and moveable. And you may move headers into and out
117538      * of a group. Note that if all sub columns are dragged out of a group, the group is destroyed.
117539      */
117540
117541     /**
117542      * @cfg {String} dataIndex
117543      * The name of the field in the grid's {@link Ext.data.Store}'s {@link Ext.data.Model} definition from
117544      * which to draw the column's value. **Required.**
117545      */
117546     dataIndex: null,
117547
117548     /**
117549      * @cfg {String} text
117550      * The header text to be used as innerHTML (html tags are accepted) to display in the Grid.
117551      * **Note**: to have a clickable header with no text displayed you can use the default of `&#160;` aka `&nbsp;`.
117552      */
117553     text: '&#160;',
117554
117555     /**
117556      * @cfg {Boolean} sortable
117557      * False to disable sorting of this column. Whether local/remote sorting is used is specified in
117558      * `{@link Ext.data.Store#remoteSort}`. Defaults to true.
117559      */
117560     sortable: true,
117561
117562     /**
117563      * @cfg {Boolean} groupable
117564      * If the grid uses a {@link Ext.grid.feature.Grouping}, this option may be used to disable the header menu
117565      * item to group by the column selected. By default, the header menu group option is enabled. Set to false to
117566      * disable (but still show) the group option in the header menu for the column.
117567      */
117568
117569     /**
117570      * @cfg {Boolean} fixed
117571      * @deprecated.
117572      * True to prevent the column from being resizable.
117573      */
117574
117575     /**
117576      * @cfg {Boolean} resizable
117577      * Set to <code>false</code> to prevent the column from being resizable. Defaults to <code>true</code>
117578      */
117579
117580     /**
117581      * @cfg {Boolean} hideable
117582      * False to prevent the user from hiding this column. Defaults to true.
117583      */
117584     hideable: true,
117585
117586     /**
117587      * @cfg {Boolean} menuDisabled
117588      * True to disable the column header menu containing sort/hide options. Defaults to false.
117589      */
117590     menuDisabled: false,
117591
117592     /**
117593      * @cfg {Function} renderer
117594      * A renderer is an 'interceptor' method which can be used transform data (value, appearance, etc.)
117595      * before it is rendered. Example:
117596      *
117597      *     {
117598      *         renderer: function(value){
117599      *             if (value === 1) {
117600      *                 return '1 person';
117601      *             }
117602      *             return value + ' people';
117603      *         }
117604      *     }
117605      *
117606      * @cfg {Object} renderer.value The data value for the current cell
117607      * @cfg {Object} renderer.metaData A collection of metadata about the current cell; can be used or modified
117608      * by the renderer. Recognized properties are: tdCls, tdAttr, and style.
117609      * @cfg {Ext.data.Model} renderer.record The record for the current row
117610      * @cfg {Number} renderer.rowIndex The index of the current row
117611      * @cfg {Number} renderer.colIndex The index of the current column
117612      * @cfg {Ext.data.Store} renderer.store The data store
117613      * @cfg {Ext.view.View} renderer.view The current view
117614      * @cfg {String} renderer.return The HTML string to be rendered.
117615      */
117616     renderer: false,
117617
117618     /**
117619      * @cfg {String} align
117620      * Sets the alignment of the header and rendered columns. Defaults to 'left'.
117621      */
117622     align: 'left',
117623
117624     /**
117625      * @cfg {Boolean} draggable
117626      * False to disable drag-drop reordering of this column. Defaults to true.
117627      */
117628     draggable: true,
117629
117630     // Header does not use the typical ComponentDraggable class and therefore we
117631     // override this with an emptyFn. It is controlled at the HeaderDragZone.
117632     initDraggable: Ext.emptyFn,
117633
117634     /**
117635      * @cfg {String} tdCls
117636      * A CSS class names to apply to the table cells for this column.
117637      */
117638
117639     /**
117640      * @cfg {Object/String} editor
117641      * An optional xtype or config object for a {@link Ext.form.field.Field Field} to use for editing.
117642      * Only applicable if the grid is using an {@link Ext.grid.plugin.Editing Editing} plugin.
117643      */
117644
117645     /**
117646      * @cfg {Object/String} field
117647      * Alias for {@link #editor}.
117648      * @deprecated 4.0.5 Use {@link #editor} instead.
117649      */
117650
117651     /**
117652      * @property {Ext.Element} triggerEl
117653      * Element that acts as button for column header dropdown menu.
117654      */
117655
117656     /**
117657      * @property {Ext.Element} textEl
117658      * Element that contains the text in column header.
117659      */
117660
117661     /**
117662      * @private
117663      * Set in this class to identify, at runtime, instances which are not instances of the
117664      * HeaderContainer base class, but are in fact, the subclass: Header.
117665      */
117666     isHeader: true,
117667
117668     initComponent: function() {
117669         var me = this,
117670             i,
117671             len,
117672             item;
117673
117674         if (Ext.isDefined(me.header)) {
117675             me.text = me.header;
117676             delete me.header;
117677         }
117678
117679         // Flexed Headers need to have a minWidth defined so that they can never be squeezed out of existence by the
117680         // HeaderContainer's specialized Box layout, the ColumnLayout. The ColumnLayout's overridden calculateChildboxes
117681         // method extends the available layout space to accommodate the "desiredWidth" of all the columns.
117682         if (me.flex) {
117683             me.minWidth = me.minWidth || Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
117684         }
117685         // Non-flexed Headers may never be squeezed in the event of a shortfall so
117686         // always set their minWidth to their current width.
117687         else {
117688             me.minWidth = me.width;
117689         }
117690
117691         if (!me.triStateSort) {
117692             me.possibleSortStates.length = 2;
117693         }
117694
117695         // A group header; It contains items which are themselves Headers
117696         if (Ext.isDefined(me.columns)) {
117697             me.isGroupHeader = true;
117698
117699             if (me.dataIndex) {
117700                 Ext.Error.raise('Ext.grid.column.Column: Group header may not accept a dataIndex');
117701             }
117702             if ((me.width && me.width !== Ext.grid.header.Container.prototype.defaultWidth) || me.flex) {
117703                 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.');
117704             }
117705
117706             // The headers become child items
117707             me.items = me.columns;
117708             delete me.columns;
117709             delete me.flex;
117710             me.width = 0;
117711
117712             // Acquire initial width from sub headers
117713             for (i = 0, len = me.items.length; i < len; i++) {
117714                 item = me.items[i];
117715                 if (!item.hidden) {
117716                     me.width += item.width || Ext.grid.header.Container.prototype.defaultWidth;
117717                 }
117718                 if (item.flex) {
117719                     Ext.Error.raise('Ext.grid.column.Column: items of a grouped header do not support flexed values. Each item must explicitly define its width.');
117720                 }
117721             }
117722             me.minWidth = me.width;
117723
117724             me.cls = (me.cls||'') + ' ' + Ext.baseCSSPrefix + 'group-header';
117725             me.sortable = false;
117726             me.resizable = false;
117727             me.align = 'center';
117728         }
117729
117730         me.addChildEls('titleContainer', 'triggerEl', 'textEl');
117731
117732         // Initialize as a HeaderContainer
117733         me.callParent(arguments);
117734     },
117735
117736     onAdd: function(childHeader) {
117737         childHeader.isSubHeader = true;
117738         childHeader.addCls(Ext.baseCSSPrefix + 'group-sub-header');
117739         this.callParent(arguments);
117740     },
117741
117742     onRemove: function(childHeader) {
117743         childHeader.isSubHeader = false;
117744         childHeader.removeCls(Ext.baseCSSPrefix + 'group-sub-header');
117745         this.callParent(arguments);
117746     },
117747
117748     initRenderData: function() {
117749         var me = this;
117750
117751         Ext.applyIf(me.renderData, {
117752             text: me.text,
117753             menuDisabled: me.menuDisabled
117754         });
117755         return me.callParent(arguments);
117756     },
117757
117758     applyColumnState: function (state) {
117759         var me = this,
117760             defined = Ext.isDefined;
117761             
117762         // apply any columns
117763         me.applyColumnsState(state.columns);
117764
117765         // Only state properties which were saved should be restored.
117766         // (Only user-changed properties were saved by getState)
117767         if (defined(state.hidden)) {
117768             me.hidden = state.hidden;
117769         }
117770         if (defined(state.locked)) {
117771             me.locked = state.locked;
117772         }
117773         if (defined(state.sortable)) {
117774             me.sortable = state.sortable;
117775         }
117776         if (defined(state.width)) {
117777             delete me.flex;
117778             me.width = state.width;
117779         } else if (defined(state.flex)) {
117780             delete me.width;
117781             me.flex = state.flex;
117782         }
117783     },
117784
117785     getColumnState: function () {
117786         var me = this,
117787             columns = [],
117788             state = {
117789                 id: me.headerId
117790             };
117791
117792         me.savePropsToState(['hidden', 'sortable', 'locked', 'flex', 'width'], state);
117793         
117794         if (me.isGroupHeader) {
117795             me.items.each(function(column){
117796                 columns.push(column.getColumnState());
117797             });
117798             if (columns.length) {
117799                 state.columns = columns;
117800             }
117801         } else if (me.isSubHeader && me.ownerCt.hidden) {
117802             // don't set hidden on the children so they can auto height
117803             delete me.hidden;
117804         }
117805
117806         if ('width' in state) {
117807             delete state.flex; // width wins
117808         }
117809         return state;
117810     },
117811
117812     /**
117813      * Sets the header text for this Column.
117814      * @param {String} text The header to display on this Column.
117815      */
117816     setText: function(text) {
117817         this.text = text;
117818         if (this.rendered) {
117819             this.textEl.update(text);
117820         }
117821     },
117822
117823     // Find the topmost HeaderContainer: An ancestor which is NOT a Header.
117824     // Group Headers are themselves HeaderContainers
117825     getOwnerHeaderCt: function() {
117826         return this.up(':not([isHeader])');
117827     },
117828
117829     /**
117830      * Returns the true grid column index associated with this column only if this column is a base level Column. If it
117831      * is a group column, it returns `false`.
117832      * @return {Number}
117833      */
117834     getIndex: function() {
117835         return this.isGroupColumn ? false : this.getOwnerHeaderCt().getHeaderIndex(this);
117836     },
117837
117838     onRender: function() {
117839         var me = this,
117840             grid = me.up('tablepanel');
117841
117842         // Disable the menu if there's nothing to show in the menu, ie:
117843         // Column cannot be sorted, grouped or locked, and there are no grid columns which may be hidden
117844         if (grid && (!me.sortable || grid.sortableColumns === false) && !me.groupable && !me.lockable && (grid.enableColumnHide === false || !me.getOwnerHeaderCt().getHideableColumns().length)) {
117845             me.menuDisabled = true;
117846         }
117847         me.callParent(arguments);
117848     },
117849
117850     afterRender: function() {
117851         var me = this,
117852             el = me.el;
117853
117854         me.callParent(arguments);
117855
117856         el.addCls(Ext.baseCSSPrefix + 'column-header-align-' + me.align).addClsOnOver(me.overCls);
117857
117858         me.mon(el, {
117859             click:     me.onElClick,
117860             dblclick:  me.onElDblClick,
117861             scope:     me
117862         });
117863
117864         // BrowserBug: Ie8 Strict Mode, this will break the focus for this browser,
117865         // must be fixed when focus management will be implemented.
117866         if (!Ext.isIE8 || !Ext.isStrict) {
117867             me.mon(me.getFocusEl(), {
117868                 focus: me.onTitleMouseOver,
117869                 blur: me.onTitleMouseOut,
117870                 scope: me
117871             });
117872         }
117873
117874         me.mon(me.titleContainer, {
117875             mouseenter:  me.onTitleMouseOver,
117876             mouseleave:  me.onTitleMouseOut,
117877             scope:      me
117878         });
117879
117880         me.keyNav = Ext.create('Ext.util.KeyNav', el, {
117881             enter: me.onEnterKey,
117882             down: me.onDownKey,
117883             scope: me
117884         });
117885     },
117886
117887     /**
117888      * Sets the width of this Column.
117889      * @param {Number} width New width.
117890      */
117891     setWidth: function(width, /* private - used internally */ doLayout) {
117892         var me = this,
117893             headerCt = me.ownerCt,
117894             siblings,
117895             len, i,
117896             oldWidth = me.getWidth(),
117897             groupWidth = 0,
117898             sibling;
117899
117900         if (width !== oldWidth) {
117901             me.oldWidth = oldWidth;
117902
117903             // Non-flexed Headers may never be squeezed in the event of a shortfall so
117904             // always set the minWidth to their current width.
117905             me.minWidth = me.width = width;
117906
117907             // Bubble size changes upwards to group headers
117908             if (headerCt.isGroupHeader) {
117909                 siblings = headerCt.items.items;
117910                 len = siblings.length;
117911
117912                 for (i = 0; i < len; i++) {
117913                     sibling = siblings[i];
117914                     if (!sibling.hidden) {
117915                         groupWidth += (sibling === me) ? width : sibling.getWidth();
117916                     }
117917                 }
117918                 headerCt.setWidth(groupWidth, doLayout);
117919             } else if (doLayout !== false) {
117920                 // Allow the owning Container to perform the sizing
117921                 headerCt.doLayout();
117922             }
117923         }
117924     },
117925
117926     afterComponentLayout: function(width, height) {
117927         var me = this,
117928             ownerHeaderCt = this.getOwnerHeaderCt();
117929
117930         me.callParent(arguments);
117931
117932         // Only changes at the base level inform the grid's HeaderContainer which will update the View
117933         // Skip this if the width is null or undefined which will be the Box layout's initial pass  through the child Components
117934         // Skip this if it's the initial size setting in which case there is no ownerheaderCt yet - that is set afterRender
117935         if (width && !me.isGroupHeader && ownerHeaderCt) {
117936             ownerHeaderCt.onHeaderResize(me, width, true);
117937         }
117938         if (me.oldWidth && (width !== me.oldWidth)) {
117939             ownerHeaderCt.fireEvent('columnresize', ownerHeaderCt, this, width);
117940         }
117941         delete me.oldWidth;
117942     },
117943
117944     // private
117945     // After the container has laid out and stretched, it calls this to correctly pad the inner to center the text vertically
117946     // Total available header height must be passed to enable padding for inner elements to be calculated.
117947     setPadding: function(headerHeight) {
117948         var me = this,
117949             lineHeight = Ext.util.TextMetrics.measure(me.textEl.dom, me.text).height;
117950
117951         // Top title containing element must stretch to match height of sibling group headers
117952         if (!me.isGroupHeader) {
117953             if (me.titleContainer.getHeight() < headerHeight) {
117954                 me.titleContainer.dom.style.height = headerHeight + 'px';
117955             }
117956         }
117957         headerHeight = me.titleContainer.getViewSize().height;
117958
117959         // Vertically center the header text in potentially vertically stretched header
117960         if (lineHeight) {
117961             me.titleContainer.setStyle({
117962                 paddingTop: Math.max(((headerHeight - lineHeight) / 2), 0) + 'px'
117963             });
117964         }
117965
117966         // Only IE needs this
117967         if (Ext.isIE && me.triggerEl) {
117968             me.triggerEl.setHeight(headerHeight);
117969         }
117970     },
117971
117972     onDestroy: function() {
117973         var me = this;
117974         // force destroy on the textEl, IE reports a leak
117975         Ext.destroy(me.textEl, me.keyNav);
117976         delete me.keyNav;
117977         me.callParent(arguments);
117978     },
117979
117980     onTitleMouseOver: function() {
117981         this.titleContainer.addCls(this.hoverCls);
117982     },
117983
117984     onTitleMouseOut: function() {
117985         this.titleContainer.removeCls(this.hoverCls);
117986     },
117987
117988     onDownKey: function(e) {
117989         if (this.triggerEl) {
117990             this.onElClick(e, this.triggerEl.dom || this.el.dom);
117991         }
117992     },
117993
117994     onEnterKey: function(e) {
117995         this.onElClick(e, this.el.dom);
117996     },
117997
117998     /**
117999      * @private
118000      * Double click
118001      * @param e
118002      * @param t
118003      */
118004     onElDblClick: function(e, t) {
118005         var me = this,
118006             ownerCt = me.ownerCt;
118007         if (ownerCt && Ext.Array.indexOf(ownerCt.items, me) !== 0 && me.isOnLeftEdge(e) ) {
118008             ownerCt.expandToFit(me.previousSibling('gridcolumn'));
118009         }
118010     },
118011
118012     onElClick: function(e, t) {
118013
118014         // The grid's docked HeaderContainer.
118015         var me = this,
118016             ownerHeaderCt = me.getOwnerHeaderCt();
118017
118018         if (ownerHeaderCt && !ownerHeaderCt.ddLock) {
118019             // Firefox doesn't check the current target in a within check.
118020             // Therefore we check the target directly and then within (ancestors)
118021             if (me.triggerEl && (e.target === me.triggerEl.dom || t === me.triggerEl.dom || e.within(me.triggerEl))) {
118022                 ownerHeaderCt.onHeaderTriggerClick(me, e, t);
118023             // if its not on the left hand edge, sort
118024             } else if (e.getKey() || (!me.isOnLeftEdge(e) && !me.isOnRightEdge(e))) {
118025                 me.toggleSortState();
118026                 ownerHeaderCt.onHeaderClick(me, e, t);
118027             }
118028         }
118029     },
118030
118031     /**
118032      * @private
118033      * Process UI events from the view. The owning TablePanel calls this method, relaying events from the TableView
118034      * @param {String} type Event type, eg 'click'
118035      * @param {Ext.view.Table} view TableView Component
118036      * @param {HTMLElement} cell Cell HtmlElement the event took place within
118037      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
118038      * @param {Number} cellIndex Cell index within the row
118039      * @param {Ext.EventObject} e Original event
118040      */
118041     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
118042         return this.fireEvent.apply(this, arguments);
118043     },
118044
118045     toggleSortState: function() {
118046         var me = this,
118047             idx,
118048             nextIdx;
118049
118050         if (me.sortable) {
118051             idx = Ext.Array.indexOf(me.possibleSortStates, me.sortState);
118052
118053             nextIdx = (idx + 1) % me.possibleSortStates.length;
118054             me.setSortState(me.possibleSortStates[nextIdx]);
118055         }
118056     },
118057
118058     doSort: function(state) {
118059         var ds = this.up('tablepanel').store;
118060         ds.sort({
118061             property: this.getSortParam(),
118062             direction: state
118063         });
118064     },
118065
118066     /**
118067      * Returns the parameter to sort upon when sorting this header. By default this returns the dataIndex and will not
118068      * need to be overriden in most cases.
118069      * @return {String}
118070      */
118071     getSortParam: function() {
118072         return this.dataIndex;
118073     },
118074
118075     //setSortState: function(state, updateUI) {
118076     //setSortState: function(state, doSort) {
118077     setSortState: function(state, skipClear, initial) {
118078         var me = this,
118079             colSortClsPrefix = Ext.baseCSSPrefix + 'column-header-sort-',
118080             ascCls = colSortClsPrefix + 'ASC',
118081             descCls = colSortClsPrefix + 'DESC',
118082             nullCls = colSortClsPrefix + 'null',
118083             ownerHeaderCt = me.getOwnerHeaderCt(),
118084             oldSortState = me.sortState;
118085
118086         if (oldSortState !== state && me.getSortParam()) {
118087             me.addCls(colSortClsPrefix + state);
118088             // don't trigger a sort on the first time, we just want to update the UI
118089             if (state && !initial) {
118090                 me.doSort(state);
118091             }
118092             switch (state) {
118093                 case 'DESC':
118094                     me.removeCls([ascCls, nullCls]);
118095                     break;
118096                 case 'ASC':
118097                     me.removeCls([descCls, nullCls]);
118098                     break;
118099                 case null:
118100                     me.removeCls([ascCls, descCls]);
118101                     break;
118102             }
118103             if (ownerHeaderCt && !me.triStateSort && !skipClear) {
118104                 ownerHeaderCt.clearOtherSortStates(me);
118105             }
118106             me.sortState = state;
118107             ownerHeaderCt.fireEvent('sortchange', ownerHeaderCt, me, state);
118108         }
118109     },
118110
118111     hide: function() {
118112         var me = this,
118113             items,
118114             len, i,
118115             lb,
118116             newWidth = 0,
118117             ownerHeaderCt = me.getOwnerHeaderCt();
118118
118119         // Hiding means setting to zero width, so cache the width
118120         me.oldWidth = me.getWidth();
118121
118122         // Hiding a group header hides itself, and then informs the HeaderContainer about its sub headers (Suppressing header layout)
118123         if (me.isGroupHeader) {
118124             items = me.items.items;
118125             me.callParent(arguments);
118126             ownerHeaderCt.onHeaderHide(me);
118127             for (i = 0, len = items.length; i < len; i++) {
118128                 items[i].hidden = true;
118129                 ownerHeaderCt.onHeaderHide(items[i], true);
118130             }
118131             return;
118132         }
118133
118134         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
118135         lb = me.ownerCt.componentLayout.layoutBusy;
118136         me.ownerCt.componentLayout.layoutBusy = true;
118137         me.callParent(arguments);
118138         me.ownerCt.componentLayout.layoutBusy = lb;
118139
118140         // Notify owning HeaderContainer
118141         ownerHeaderCt.onHeaderHide(me);
118142
118143         if (me.ownerCt.isGroupHeader) {
118144             // If we've just hidden the last header in a group, then hide the group
118145             items = me.ownerCt.query('>:not([hidden])');
118146             if (!items.length) {
118147                 me.ownerCt.hide();
118148             }
118149             // Size the group down to accommodate fewer sub headers
118150             else {
118151                 for (i = 0, len = items.length; i < len; i++) {
118152                     newWidth += items[i].getWidth();
118153                 }
118154                 me.ownerCt.minWidth = newWidth;
118155                 me.ownerCt.setWidth(newWidth);
118156             }
118157         }
118158     },
118159
118160     show: function() {
118161         var me = this,
118162             ownerCt = me.ownerCt,
118163             ownerCtCompLayout = ownerCt.componentLayout,
118164             ownerCtCompLayoutBusy = ownerCtCompLayout.layoutBusy,
118165             ownerCtLayout = ownerCt.layout,
118166             ownerCtLayoutBusy = ownerCtLayout.layoutBusy,
118167             items,
118168             len, i,
118169             item,
118170             newWidth = 0;
118171
118172         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
118173
118174         // Suspend our owner's layouts (both component and container):
118175         ownerCtCompLayout.layoutBusy = ownerCtLayout.layoutBusy = true;
118176
118177         me.callParent(arguments);
118178
118179         ownerCtCompLayout.layoutBusy = ownerCtCompLayoutBusy;
118180         ownerCtLayout.layoutBusy = ownerCtLayoutBusy;
118181
118182         // If a sub header, ensure that the group header is visible
118183         if (me.isSubHeader) {
118184             if (!ownerCt.isVisible()) {
118185                 ownerCt.show();
118186             }
118187         }
118188
118189         // If we've just shown a group with all its sub headers hidden, then show all its sub headers
118190         if (me.isGroupHeader && !me.query(':not([hidden])').length) {
118191             items = me.query('>*');
118192             for (i = 0, len = items.length; i < len; i++) {
118193                 item = items[i];
118194                 item.preventLayout = true;
118195                 item.show();
118196                 newWidth += item.getWidth();
118197                 delete item.preventLayout;
118198             }
118199             me.setWidth(newWidth);
118200         }
118201
118202         // Resize the owning group to accommodate
118203         if (ownerCt.isGroupHeader && me.preventLayout !== true) {
118204             items = ownerCt.query('>:not([hidden])');
118205             for (i = 0, len = items.length; i < len; i++) {
118206                 newWidth += items[i].getWidth();
118207             }
118208             ownerCt.minWidth = newWidth;
118209             ownerCt.setWidth(newWidth);
118210         }
118211
118212         // Notify owning HeaderContainer
118213         ownerCt = me.getOwnerHeaderCt();
118214         if (ownerCt) {
118215             ownerCt.onHeaderShow(me, me.preventLayout);
118216         }
118217     },
118218
118219     getDesiredWidth: function() {
118220         var me = this;
118221         if (me.rendered && me.componentLayout && me.componentLayout.lastComponentSize) {
118222             // headers always have either a width or a flex
118223             // because HeaderContainer sets a defaults width
118224             // therefore we can ignore the natural width
118225             // we use the componentLayout's tracked width so that
118226             // we can calculate the desired width when rendered
118227             // but not visible because its being obscured by a layout
118228             return me.componentLayout.lastComponentSize.width;
118229         // Flexed but yet to be rendered this could be the case
118230         // where a HeaderContainer and Headers are simply used as data
118231         // structures and not rendered.
118232         }
118233         else if (me.flex) {
118234             // this is going to be wrong, the defaultWidth
118235             return me.width;
118236         }
118237         else {
118238             return me.width;
118239         }
118240     },
118241
118242     getCellSelector: function() {
118243         return '.' + Ext.baseCSSPrefix + 'grid-cell-' + this.getItemId();
118244     },
118245
118246     getCellInnerSelector: function() {
118247         return this.getCellSelector() + ' .' + Ext.baseCSSPrefix + 'grid-cell-inner';
118248     },
118249
118250     isOnLeftEdge: function(e) {
118251         return (e.getXY()[0] - this.el.getLeft() <= this.handleWidth);
118252     },
118253
118254     isOnRightEdge: function(e) {
118255         return (this.el.getRight() - e.getXY()[0] <= this.handleWidth);
118256     }
118257
118258     // intentionally omit getEditor and setEditor definitions bc we applyIf into columns
118259     // when the editing plugin is injected
118260
118261     /**
118262      * @method getEditor
118263      * Retrieves the editing field for editing associated with this header. Returns false if there is no field
118264      * associated with the Header the method will return false. If the field has not been instantiated it will be
118265      * created. Note: These methods only has an implementation if a Editing plugin has been enabled on the grid.
118266      * @param {Object} record The {@link Ext.data.Model Model} instance being edited.
118267      * @param {Object} defaultField An object representing a default field to be created
118268      * @return {Ext.form.field.Field} field
118269      */
118270     /**
118271      * @method setEditor
118272      * Sets the form field to be used for editing. Note: This method only has an implementation if an Editing plugin has
118273      * been enabled on the grid.
118274      * @param {Object} field An object representing a field to be created. If no xtype is specified a 'textfield' is
118275      * assumed.
118276      */
118277 });
118278
118279 /**
118280  * This is a utility class that can be passed into a {@link Ext.grid.column.Column} as a column config that provides
118281  * an automatic row numbering column.
118282  * 
118283  * Usage:
118284  *
118285  *     columns: [
118286  *         {xtype: 'rownumberer'},
118287  *         {text: "Company", flex: 1, sortable: true, dataIndex: 'company'},
118288  *         {text: "Price", width: 120, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
118289  *         {text: "Change", width: 120, sortable: true, dataIndex: 'change'},
118290  *         {text: "% Change", width: 120, sortable: true, dataIndex: 'pctChange'},
118291  *         {text: "Last Updated", width: 120, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
118292  *     ]
118293  *
118294  */
118295 Ext.define('Ext.grid.RowNumberer', {
118296     extend: 'Ext.grid.column.Column',
118297     alias: 'widget.rownumberer',
118298
118299     /**
118300      * @cfg {String} text
118301      * Any valid text or HTML fragment to display in the header cell for the row number column.
118302      */
118303     text: "&#160",
118304
118305     /**
118306      * @cfg {Number} width
118307      * The default width in pixels of the row number column.
118308      */
118309     width: 23,
118310
118311     /**
118312      * @cfg {Boolean} sortable
118313      * True if the row number column is sortable.
118314      * @hide
118315      */
118316     sortable: false,
118317
118318     align: 'right',
118319
118320     constructor : function(config){
118321         this.callParent(arguments);
118322         if (this.rowspan) {
118323             this.renderer = Ext.Function.bind(this.renderer, this);
118324         }
118325     },
118326
118327     // private
118328     resizable: false,
118329     hideable: false,
118330     menuDisabled: true,
118331     dataIndex: '',
118332     cls: Ext.baseCSSPrefix + 'row-numberer',
118333     rowspan: undefined,
118334
118335     // private
118336     renderer: function(value, metaData, record, rowIdx, colIdx, store) {
118337         if (this.rowspan){
118338             metaData.cellAttr = 'rowspan="'+this.rowspan+'"';
118339         }
118340
118341         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
118342         return store.indexOfTotal(record) + 1;
118343     }
118344 });
118345
118346 /**
118347  * @class Ext.view.DropZone
118348  * @extends Ext.dd.DropZone
118349  * @private
118350  */
118351 Ext.define('Ext.view.DropZone', {
118352     extend: 'Ext.dd.DropZone',
118353
118354     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
118355     indicatorCls: 'x-grid-drop-indicator',
118356
118357     constructor: function(config) {
118358         var me = this;
118359         Ext.apply(me, config);
118360
118361         // Create a ddGroup unless one has been configured.
118362         // User configuration of ddGroups allows users to specify which
118363         // DD instances can interact with each other. Using one
118364         // based on the id of the View would isolate it and mean it can only
118365         // interact with a DragZone on the same View also using a generated ID.
118366         if (!me.ddGroup) {
118367             me.ddGroup = 'view-dd-zone-' + me.view.id;
118368         }
118369
118370         // The DropZone's encapsulating element is the View's main element. It must be this because drop gestures
118371         // may require scrolling on hover near a scrolling boundary. In Ext 4.x two DD instances may not use the
118372         // same element, so a DragZone on this same View must use the View's parent element as its element.
118373         me.callParent([me.view.el]);
118374     },
118375
118376 //  Fire an event through the client DataView. Lock this DropZone during the event processing so that
118377 //  its data does not become corrupted by processing mouse events.
118378     fireViewEvent: function() {
118379         var me = this,
118380             result;
118381
118382         me.lock();
118383         result = me.view.fireEvent.apply(me.view, arguments);
118384         me.unlock();
118385         return result;
118386     },
118387
118388     getTargetFromEvent : function(e) {
118389         var node = e.getTarget(this.view.getItemSelector()),
118390             mouseY, nodeList, testNode, i, len, box;
118391
118392 //      Not over a row node: The content may be narrower than the View's encapsulating element, so return the closest.
118393 //      If we fall through because the mouse is below the nodes (or there are no nodes), we'll get an onContainerOver call.
118394         if (!node) {
118395             mouseY = e.getPageY();
118396             for (i = 0, nodeList = this.view.getNodes(), len = nodeList.length; i < len; i++) {
118397                 testNode = nodeList[i];
118398                 box = Ext.fly(testNode).getBox();
118399                 if (mouseY <= box.bottom) {
118400                     return testNode;
118401                 }
118402             }
118403         }
118404         return node;
118405     },
118406
118407     getIndicator: function() {
118408         var me = this;
118409
118410         if (!me.indicator) {
118411             me.indicator = Ext.createWidget('component', {
118412                 html: me.indicatorHtml,
118413                 cls: me.indicatorCls,
118414                 ownerCt: me.view,
118415                 floating: true,
118416                 shadow: false
118417             });
118418         }
118419         return me.indicator;
118420     },
118421
118422     getPosition: function(e, node) {
118423         var y      = e.getXY()[1],
118424             region = Ext.fly(node).getRegion(),
118425             pos;
118426
118427         if ((region.bottom - y) >= (region.bottom - region.top) / 2) {
118428             pos = "before";
118429         } else {
118430             pos = "after";
118431         }
118432         return pos;
118433     },
118434
118435     /**
118436      * @private Determines whether the record at the specified offset from the passed record
118437      * is in the drag payload.
118438      * @param records
118439      * @param record
118440      * @param offset
118441      * @returns {Boolean} True if the targeted record is in the drag payload
118442      */
118443     containsRecordAtOffset: function(records, record, offset) {
118444         if (!record) {
118445             return false;
118446         }
118447         var view = this.view,
118448             recordIndex = view.indexOf(record),
118449             nodeBefore = view.getNode(recordIndex + offset),
118450             recordBefore = nodeBefore ? view.getRecord(nodeBefore) : null;
118451
118452         return recordBefore && Ext.Array.contains(records, recordBefore);
118453     },
118454
118455     positionIndicator: function(node, data, e) {
118456         var me = this,
118457             view = me.view,
118458             pos = me.getPosition(e, node),
118459             overRecord = view.getRecord(node),
118460             draggingRecords = data.records,
118461             indicator, indicatorY;
118462
118463         if (!Ext.Array.contains(draggingRecords, overRecord) && (
118464             pos == 'before' && !me.containsRecordAtOffset(draggingRecords, overRecord, -1) ||
118465             pos == 'after' && !me.containsRecordAtOffset(draggingRecords, overRecord, 1)
118466         )) {
118467             me.valid = true;
118468
118469             if (me.overRecord != overRecord || me.currentPosition != pos) {
118470
118471                 indicatorY = Ext.fly(node).getY() - view.el.getY() - 1;
118472                 if (pos == 'after') {
118473                     indicatorY += Ext.fly(node).getHeight();
118474                 }
118475                 me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, indicatorY);
118476
118477                 // Cache the overRecord and the 'before' or 'after' indicator.
118478                 me.overRecord = overRecord;
118479                 me.currentPosition = pos;
118480             }
118481         } else {
118482             me.invalidateDrop();
118483         }
118484     },
118485
118486     invalidateDrop: function() {
118487         if (this.valid) {
118488             this.valid = false;
118489             this.getIndicator().hide();
118490         }
118491     },
118492
118493     // The mouse is over a View node
118494     onNodeOver: function(node, dragZone, e, data) {
118495         var me = this;
118496
118497         if (!Ext.Array.contains(data.records, me.view.getRecord(node))) {
118498             me.positionIndicator(node, data, e);
118499         }
118500         return me.valid ? me.dropAllowed : me.dropNotAllowed;
118501     },
118502
118503     // Moved out of the DropZone without dropping.
118504     // Remove drop position indicator
118505     notifyOut: function(node, dragZone, e, data) {
118506         var me = this;
118507
118508         me.callParent(arguments);
118509         delete me.overRecord;
118510         delete me.currentPosition;
118511         if (me.indicator) {
118512             me.indicator.hide();
118513         }
118514     },
118515
118516     // The mouse is past the end of all nodes (or there are no nodes)
118517     onContainerOver : function(dd, e, data) {
118518         var me = this,
118519             view = me.view,
118520             count = view.store.getCount();
118521
118522         // There are records, so position after the last one
118523         if (count) {
118524             me.positionIndicator(view.getNode(count - 1), data, e);
118525         }
118526
118527         // No records, position the indicator at the top
118528         else {
118529             delete me.overRecord;
118530             delete me.currentPosition;
118531             me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, 0);
118532             me.valid = true;
118533         }
118534         return me.dropAllowed;
118535     },
118536
118537     onContainerDrop : function(dd, e, data) {
118538         return this.onNodeDrop(dd, null, e, data);
118539     },
118540
118541     onNodeDrop: function(node, dragZone, e, data) {
118542         var me = this,
118543             dropped = false,
118544
118545             // Create a closure to perform the operation which the event handler may use.
118546             // Users may now return <code>false</code> from the beforedrop handler, and perform any kind
118547             // of asynchronous processing such as an Ext.Msg.confirm, or an Ajax request,
118548             // and complete the drop gesture at some point in the future by calling this function.
118549             processDrop = function () {
118550                 me.invalidateDrop();
118551                 me.handleNodeDrop(data, me.overRecord, me.currentPosition);
118552                 dropped = true;
118553                 me.fireViewEvent('drop', node, data, me.overRecord, me.currentPosition);
118554             },
118555             performOperation = false;
118556
118557         if (me.valid) {
118558             performOperation = me.fireViewEvent('beforedrop', node, data, me.overRecord, me.currentPosition, processDrop);
118559             if (performOperation !== false) {
118560                 // If the processDrop function was called in the event handler, do not do it again.
118561                 if (!dropped) {
118562                     processDrop();
118563                 }
118564             }
118565         }
118566         return performOperation;
118567     },
118568     
118569     destroy: function(){
118570         Ext.destroy(this.indicator);
118571         delete this.indicator;
118572         this.callParent();
118573     }
118574 });
118575
118576 Ext.define('Ext.grid.ViewDropZone', {
118577     extend: 'Ext.view.DropZone',
118578
118579     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
118580     indicatorCls: 'x-grid-drop-indicator',
118581
118582     handleNodeDrop : function(data, record, position) {
118583         var view = this.view,
118584             store = view.getStore(),
118585             index, records, i, len;
118586
118587         // If the copy flag is set, create a copy of the Models with the same IDs
118588         if (data.copy) {
118589             records = data.records;
118590             data.records = [];
118591             for (i = 0, len = records.length; i < len; i++) {
118592                 data.records.push(records[i].copy(records[i].getId()));
118593             }
118594         } else {
118595             /*
118596              * Remove from the source store. We do this regardless of whether the store
118597              * is the same bacsue the store currently doesn't handle moving records
118598              * within the store. In the future it should be possible to do this.
118599              * Here was pass the isMove parameter if we're moving to the same view.
118600              */
118601             data.view.store.remove(data.records, data.view === view);
118602         }
118603
118604         index = store.indexOf(record);
118605
118606         // 'after', or undefined (meaning a drop at index -1 on an empty View)...
118607         if (position !== 'before') {
118608             index++;
118609         }
118610         store.insert(index, data.records);
118611         view.getSelectionModel().select(data.records);
118612     }
118613 });
118614 /**
118615  * A Grid header type which renders an icon, or a series of icons in a grid cell, and offers a scoped click
118616  * handler for each icon.
118617  *
118618  *     @example
118619  *     Ext.create('Ext.data.Store', {
118620  *         storeId:'employeeStore',
118621  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
118622  *         data:[
118623  *             {firstname:"Michael", lastname:"Scott"},
118624  *             {firstname:"Dwight", lastname:"Schrute"},
118625  *             {firstname:"Jim", lastname:"Halpert"},
118626  *             {firstname:"Kevin", lastname:"Malone"},
118627  *             {firstname:"Angela", lastname:"Martin"}
118628  *         ]
118629  *     });
118630  *
118631  *     Ext.create('Ext.grid.Panel', {
118632  *         title: 'Action Column Demo',
118633  *         store: Ext.data.StoreManager.lookup('employeeStore'),
118634  *         columns: [
118635  *             {text: 'First Name',  dataIndex:'firstname'},
118636  *             {text: 'Last Name',  dataIndex:'lastname'},
118637  *             {
118638  *                 xtype:'actioncolumn',
118639  *                 width:50,
118640  *                 items: [{
118641  *                     icon: 'extjs/examples/shared/icons/fam/cog_edit.png',  // Use a URL in the icon config
118642  *                     tooltip: 'Edit',
118643  *                     handler: function(grid, rowIndex, colIndex) {
118644  *                         var rec = grid.getStore().getAt(rowIndex);
118645  *                         alert("Edit " + rec.get('firstname'));
118646  *                     }
118647  *                 },{
118648  *                     icon: 'extjs/examples/restful/images/delete.png',
118649  *                     tooltip: 'Delete',
118650  *                     handler: function(grid, rowIndex, colIndex) {
118651  *                         var rec = grid.getStore().getAt(rowIndex);
118652  *                         alert("Terminate " + rec.get('firstname'));
118653  *                     }
118654  *                 }]
118655  *             }
118656  *         ],
118657  *         width: 250,
118658  *         renderTo: Ext.getBody()
118659  *     });
118660  *
118661  * The action column can be at any index in the columns array, and a grid can have any number of
118662  * action columns.
118663  */
118664 Ext.define('Ext.grid.column.Action', {
118665     extend: 'Ext.grid.column.Column',
118666     alias: ['widget.actioncolumn'],
118667     alternateClassName: 'Ext.grid.ActionColumn',
118668
118669     /**
118670      * @cfg {String} icon
118671      * The URL of an image to display as the clickable element in the column. Defaults to
118672      * `{@link Ext#BLANK_IMAGE_URL Ext.BLANK_IMAGE_URL}`.
118673      */
118674     /**
118675      * @cfg {String} iconCls
118676      * A CSS class to apply to the icon image. To determine the class dynamically, configure the Column with
118677      * a `{@link #getClass}` function.
118678      */
118679     /**
118680      * @cfg {Function} handler
118681      * A function called when the icon is clicked.
118682      * @cfg {Ext.view.Table} handler.view The owning TableView.
118683      * @cfg {Number} handler.rowIndex The row index clicked on.
118684      * @cfg {Number} handler.colIndex The column index clicked on.
118685      * @cfg {Object} handler.item The clicked item (or this Column if multiple {@link #items} were not configured).
118686      * @cfg {Event} handler.e The click event.
118687      */
118688     /**
118689      * @cfg {Object} scope
118690      * The scope (**this** reference) in which the `{@link #handler}` and `{@link #getClass}` fuctions are executed.
118691      * Defaults to this Column.
118692      */
118693     /**
118694      * @cfg {String} tooltip
118695      * A tooltip message to be displayed on hover. {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must
118696      * have been initialized.
118697      */
118698     /* @cfg {Boolean} disabled
118699      * If true, the action will not respond to click events, and will be displayed semi-opaque.
118700      */
118701     /**
118702      * @cfg {Boolean} [stopSelection=true]
118703      * Prevent grid _row_ selection upon mousedown.
118704      */
118705     /**
118706      * @cfg {Function} getClass
118707      * A function which returns the CSS class to apply to the icon image.
118708      *
118709      * @cfg {Object} getClass.v The value of the column's configured field (if any).
118710      *
118711      * @cfg {Object} getClass.metadata An object in which you may set the following attributes:
118712      * @cfg {String} getClass.metadata.css A CSS class name to add to the cell's TD element.
118713      * @cfg {String} getClass.metadata.attr An HTML attribute definition string to apply to the data container
118714      * element *within* the table cell (e.g. 'style="color:red;"').
118715      *
118716      * @cfg {Ext.data.Model} getClass.r The Record providing the data.
118717      *
118718      * @cfg {Number} getClass.rowIndex The row index..
118719      *
118720      * @cfg {Number} getClass.colIndex The column index.
118721      *
118722      * @cfg {Ext.data.Store} getClass.store The Store which is providing the data Model.
118723      */
118724     /**
118725      * @cfg {Object[]} items
118726      * An Array which may contain multiple icon definitions, each element of which may contain:
118727      *
118728      * @cfg {String} items.icon The url of an image to display as the clickable element in the column.
118729      *
118730      * @cfg {String} items.iconCls A CSS class to apply to the icon image. To determine the class dynamically,
118731      * configure the item with a `getClass` function.
118732      *
118733      * @cfg {Function} items.getClass A function which returns the CSS class to apply to the icon image.
118734      * @cfg {Object} items.getClass.v The value of the column's configured field (if any).
118735      * @cfg {Object} items.getClass.metadata An object in which you may set the following attributes:
118736      * @cfg {String} items.getClass.metadata.css A CSS class name to add to the cell's TD element.
118737      * @cfg {String} items.getClass.metadata.attr An HTML attribute definition string to apply to the data
118738      * container element _within_ the table cell (e.g. 'style="color:red;"').
118739      * @cfg {Ext.data.Model} items.getClass.r The Record providing the data.
118740      * @cfg {Number} items.getClass.rowIndex The row index..
118741      * @cfg {Number} items.getClass.colIndex The column index.
118742      * @cfg {Ext.data.Store} items.getClass.store The Store which is providing the data Model.
118743      *
118744      * @cfg {Function} items.handler A function called when the icon is clicked.
118745      *
118746      * @cfg {Object} items.scope The scope (`this` reference) in which the `handler` and `getClass` functions
118747      * are executed. Fallback defaults are this Column's configured scope, then this Column.
118748      *
118749      * @cfg {String} items.tooltip A tooltip message to be displayed on hover.
118750      * @cfg {Boolean} items.disabled If true, the action will not respond to click events, and will be displayed semi-opaque.
118751      * {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must have been initialized.
118752      */
118753     /**
118754      * @property {Array} items
118755      * An array of action items copied from the configured {@link #cfg-items items} configuration. Each will have
118756      * an `enable` and `disable` method added which will enable and disable the associated action, and
118757      * update the displayed icon accordingly.
118758      */
118759     header: '&#160;',
118760
118761     actionIdRe: new RegExp(Ext.baseCSSPrefix + 'action-col-(\\d+)'),
118762
118763     /**
118764      * @cfg {String} altText
118765      * The alt text to use for the image element.
118766      */
118767     altText: '',
118768
118769     sortable: false,
118770
118771     constructor: function(config) {
118772         var me = this,
118773             cfg = Ext.apply({}, config),
118774             items = cfg.items || [me],
118775             l = items.length,
118776             i,
118777             item;
118778
118779         // This is a Container. Delete the items config to be reinstated after construction.
118780         delete cfg.items;
118781         me.callParent([cfg]);
118782
118783         // Items is an array property of ActionColumns
118784         me.items = items;
118785
118786 //      Renderer closure iterates through items creating an <img> element for each and tagging with an identifying
118787 //      class name x-action-col-{n}
118788         me.renderer = function(v, meta) {
118789 //          Allow a configured renderer to create initial value (And set the other values in the "metadata" argument!)
118790             v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '';
118791
118792             meta.tdCls += ' ' + Ext.baseCSSPrefix + 'action-col-cell';
118793             for (i = 0; i < l; i++) {
118794                 item = items[i];
118795                 item.disable = Ext.Function.bind(me.disableAction, me, [i]);
118796                 item.enable = Ext.Function.bind(me.enableAction, me, [i]);
118797                 v += '<img alt="' + (item.altText || me.altText) + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
118798                     '" class="' + Ext.baseCSSPrefix + 'action-col-icon ' + Ext.baseCSSPrefix + 'action-col-' + String(i) + ' ' + (item.disabled ? Ext.baseCSSPrefix + 'item-disabled' : ' ') + (item.iconCls || '') +
118799                     ' ' + (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope||me.scope||me, arguments) : (me.iconCls || '')) + '"' +
118800                     ((item.tooltip) ? ' data-qtip="' + item.tooltip + '"' : '') + ' />';
118801             }
118802             return v;
118803         };
118804     },
118805
118806     /**
118807      * Enables this ActionColumn's action at the specified index.
118808      */
118809     enableAction: function(index) {
118810         var me = this;
118811
118812         if (!index) {
118813             index = 0;
118814         } else if (!Ext.isNumber(index)) {
118815             index = Ext.Array.indexOf(me.items, index);
118816         }
118817         me.items[index].disabled = false;
118818         me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).removeCls(me.disabledCls);
118819     },
118820
118821     /**
118822      * Disables this ActionColumn's action at the specified index.
118823      */
118824     disableAction: function(index) {
118825         var me = this;
118826
118827         if (!index) {
118828             index = 0;
118829         } else if (!Ext.isNumber(index)) {
118830             index = Ext.Array.indexOf(me.items, index);
118831         }
118832         me.items[index].disabled = true;
118833         me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).addCls(me.disabledCls);
118834     },
118835
118836     destroy: function() {
118837         delete this.items;
118838         delete this.renderer;
118839         return this.callParent(arguments);
118840     },
118841
118842     /**
118843      * @private
118844      * Process and refire events routed from the GridView's processEvent method.
118845      * Also fires any configured click handlers. By default, cancels the mousedown event to prevent selection.
118846      * Returns the event handler's status to allow canceling of GridView's bubbling process.
118847      */
118848     processEvent : function(type, view, cell, recordIndex, cellIndex, e){
118849         var me = this,
118850             match = e.getTarget().className.match(me.actionIdRe),
118851             item, fn;
118852             
118853         if (match) {
118854             item = me.items[parseInt(match[1], 10)];
118855             if (item) {
118856                 if (type == 'click') {
118857                     fn = item.handler || me.handler;
118858                     if (fn && !item.disabled) {
118859                         fn.call(item.scope || me.scope || me, view, recordIndex, cellIndex, item, e);
118860                     }
118861                 } else if (type == 'mousedown' && item.stopSelection !== false) {
118862                     return false;
118863                 }
118864             }
118865         }
118866         return me.callParent(arguments);
118867     },
118868
118869     cascade: function(fn, scope) {
118870         fn.call(scope||this, this);
118871     },
118872
118873     // Private override because this cannot function as a Container, and it has an items property which is an Array, NOT a MixedCollection.
118874     getRefItems: function() {
118875         return [];
118876     }
118877 });
118878 /**
118879  * A Column definition class which renders boolean data fields.  See the {@link Ext.grid.column.Column#xtype xtype}
118880  * config option of {@link Ext.grid.column.Column} for more details.
118881  *
118882  *     @example
118883  *     Ext.create('Ext.data.Store', {
118884  *        storeId:'sampleStore',
118885  *        fields:[
118886  *            {name: 'framework', type: 'string'},
118887  *            {name: 'rocks', type: 'boolean'}
118888  *        ],
118889  *        data:{'items':[
118890  *            { 'framework': "Ext JS 4",     'rocks': true  },
118891  *            { 'framework': "Sencha Touch", 'rocks': true  },
118892  *            { 'framework': "Ext GWT",      'rocks': true  }, 
118893  *            { 'framework': "Other Guys",   'rocks': false } 
118894  *        ]},
118895  *        proxy: {
118896  *            type: 'memory',
118897  *            reader: {
118898  *                type: 'json',
118899  *                root: 'items'
118900  *            }
118901  *        }
118902  *     });
118903  *     
118904  *     Ext.create('Ext.grid.Panel', {
118905  *         title: 'Boolean Column Demo',
118906  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118907  *         columns: [
118908  *             { text: 'Framework',  dataIndex: 'framework', flex: 1 },
118909  *             {
118910  *                 xtype: 'booleancolumn', 
118911  *                 text: 'Rocks',
118912  *                 trueText: 'Yes',
118913  *                 falseText: 'No', 
118914  *                 dataIndex: 'rocks'
118915  *             }
118916  *         ],
118917  *         height: 200,
118918  *         width: 400,
118919  *         renderTo: Ext.getBody()
118920  *     });
118921  */
118922 Ext.define('Ext.grid.column.Boolean', {
118923     extend: 'Ext.grid.column.Column',
118924     alias: ['widget.booleancolumn'],
118925     alternateClassName: 'Ext.grid.BooleanColumn',
118926
118927     /**
118928      * @cfg {String} trueText
118929      * The string returned by the renderer when the column value is not falsey.
118930      */
118931     trueText: 'true',
118932
118933     /**
118934      * @cfg {String} falseText
118935      * The string returned by the renderer when the column value is falsey (but not undefined).
118936      */
118937     falseText: 'false',
118938
118939     /**
118940      * @cfg {String} undefinedText
118941      * The string returned by the renderer when the column value is undefined.
118942      */
118943     undefinedText: '&#160;',
118944
118945     constructor: function(cfg){
118946         this.callParent(arguments);
118947         var trueText      = this.trueText,
118948             falseText     = this.falseText,
118949             undefinedText = this.undefinedText;
118950
118951         this.renderer = function(value){
118952             if(value === undefined){
118953                 return undefinedText;
118954             }
118955             if(!value || value === 'false'){
118956                 return falseText;
118957             }
118958             return trueText;
118959         };
118960     }
118961 });
118962 /**
118963  * A Column definition class which renders a passed date according to the default locale, or a configured
118964  * {@link #format}.
118965  *
118966  *     @example
118967  *     Ext.create('Ext.data.Store', {
118968  *         storeId:'sampleStore',
118969  *         fields:[
118970  *             { name: 'symbol', type: 'string' },
118971  *             { name: 'date',   type: 'date' },
118972  *             { name: 'change', type: 'number' },
118973  *             { name: 'volume', type: 'number' },
118974  *             { name: 'topday', type: 'date' }                        
118975  *         ],
118976  *         data:[
118977  *             { symbol: "msft",   date: '2011/04/22', change: 2.43, volume: 61606325, topday: '04/01/2010' },
118978  *             { symbol: "goog",   date: '2011/04/22', change: 0.81, volume: 3053782,  topday: '04/11/2010' },
118979  *             { symbol: "apple",  date: '2011/04/22', change: 1.35, volume: 24484858, topday: '04/28/2010' },            
118980  *             { symbol: "sencha", date: '2011/04/22', change: 8.85, volume: 5556351,  topday: '04/22/2010' }            
118981  *         ]
118982  *     });
118983  *     
118984  *     Ext.create('Ext.grid.Panel', {
118985  *         title: 'Date Column Demo',
118986  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118987  *         columns: [
118988  *             { text: 'Symbol',   dataIndex: 'symbol', flex: 1 },
118989  *             { text: 'Date',     dataIndex: 'date',   xtype: 'datecolumn',   format:'Y-m-d' },
118990  *             { text: 'Change',   dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
118991  *             { text: 'Volume',   dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' },
118992  *             { text: 'Top Day',  dataIndex: 'topday', xtype: 'datecolumn',   format:'l' }            
118993  *         ],
118994  *         height: 200,
118995  *         width: 450,
118996  *         renderTo: Ext.getBody()
118997  *     });
118998  */
118999 Ext.define('Ext.grid.column.Date', {
119000     extend: 'Ext.grid.column.Column',
119001     alias: ['widget.datecolumn'],
119002     requires: ['Ext.Date'],
119003     alternateClassName: 'Ext.grid.DateColumn',
119004
119005     /**
119006      * @cfg {String} format
119007      * A formatting string as used by {@link Ext.Date#format} to format a Date for this Column.
119008      * This defaults to the default date from {@link Ext.Date#defaultFormat} which itself my be overridden
119009      * in a locale file.
119010      */
119011
119012     initComponent: function(){
119013         var me = this;
119014         
119015         me.callParent(arguments);
119016         if (!me.format) {
119017             me.format = Ext.Date.defaultFormat;
119018         }
119019         me.renderer = Ext.util.Format.dateRenderer(me.format);
119020     }
119021 });
119022 /**
119023  * A Column definition class which renders a numeric data field according to a {@link #format} string.
119024  *
119025  *     @example
119026  *     Ext.create('Ext.data.Store', {
119027  *        storeId:'sampleStore',
119028  *        fields:[
119029  *            { name: 'symbol', type: 'string' },
119030  *            { name: 'price',  type: 'number' },
119031  *            { name: 'change', type: 'number' },
119032  *            { name: 'volume', type: 'number' },            
119033  *        ],
119034  *        data:[
119035  *            { symbol: "msft",   price: 25.76,  change: 2.43, volume: 61606325 },
119036  *            { symbol: "goog",   price: 525.73, change: 0.81, volume: 3053782  },
119037  *            { symbol: "apple",  price: 342.41, change: 1.35, volume: 24484858 },            
119038  *            { symbol: "sencha", price: 142.08, change: 8.85, volume: 5556351  }            
119039  *        ]
119040  *     });
119041  *     
119042  *     Ext.create('Ext.grid.Panel', {
119043  *         title: 'Number Column Demo',
119044  *         store: Ext.data.StoreManager.lookup('sampleStore'),
119045  *         columns: [
119046  *             { text: 'Symbol',         dataIndex: 'symbol', flex: 1 },
119047  *             { text: 'Current Price',  dataIndex: 'price',  renderer: Ext.util.Format.usMoney },
119048  *             { text: 'Change',         dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
119049  *             { text: 'Volume',         dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' }
119050  *         ],
119051  *         height: 200,
119052  *         width: 400,
119053  *         renderTo: Ext.getBody()
119054  *     });
119055  */
119056 Ext.define('Ext.grid.column.Number', {
119057     extend: 'Ext.grid.column.Column',
119058     alias: ['widget.numbercolumn'],
119059     requires: ['Ext.util.Format'],
119060     alternateClassName: 'Ext.grid.NumberColumn',
119061
119062     /**
119063      * @cfg {String} format
119064      * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column.
119065      */
119066     format : '0,000.00',
119067
119068     constructor: function(cfg) {
119069         this.callParent(arguments);
119070         this.renderer = Ext.util.Format.numberRenderer(this.format);
119071     }
119072 });
119073 /**
119074  * A Column definition class which renders a value by processing a {@link Ext.data.Model Model}'s
119075  * {@link Ext.data.Model#persistenceProperty data} using a {@link #tpl configured}
119076  * {@link Ext.XTemplate XTemplate}.
119077  * 
119078  *     @example
119079  *     Ext.create('Ext.data.Store', {
119080  *         storeId:'employeeStore',
119081  *         fields:['firstname', 'lastname', 'senority', 'department'],
119082  *         groupField: 'department',
119083  *         data:[
119084  *             { firstname: "Michael", lastname: "Scott",   senority: 7, department: "Manangement" },
119085  *             { firstname: "Dwight",  lastname: "Schrute", senority: 2, department: "Sales" },
119086  *             { firstname: "Jim",     lastname: "Halpert", senority: 3, department: "Sales" },
119087  *             { firstname: "Kevin",   lastname: "Malone",  senority: 4, department: "Accounting" },
119088  *             { firstname: "Angela",  lastname: "Martin",  senority: 5, department: "Accounting" }                        
119089  *         ]
119090  *     });
119091  *     
119092  *     Ext.create('Ext.grid.Panel', {
119093  *         title: 'Column Template Demo',
119094  *         store: Ext.data.StoreManager.lookup('employeeStore'),
119095  *         columns: [
119096  *             { text: 'Full Name',       xtype: 'templatecolumn', tpl: '{firstname} {lastname}', flex:1 },
119097  *             { text: 'Deparment (Yrs)', xtype: 'templatecolumn', tpl: '{department} ({senority})' }
119098  *         ],
119099  *         height: 200,
119100  *         width: 300,
119101  *         renderTo: Ext.getBody()
119102  *     });
119103  */
119104 Ext.define('Ext.grid.column.Template', {
119105     extend: 'Ext.grid.column.Column',
119106     alias: ['widget.templatecolumn'],
119107     requires: ['Ext.XTemplate'],
119108     alternateClassName: 'Ext.grid.TemplateColumn',
119109
119110     /**
119111      * @cfg {String/Ext.XTemplate} tpl
119112      * An {@link Ext.XTemplate XTemplate}, or an XTemplate *definition string* to use to process a
119113      * {@link Ext.data.Model Model}'s {@link Ext.data.Model#persistenceProperty data} to produce a
119114      * column's rendered value.
119115      */
119116
119117     constructor: function(cfg){
119118         var me = this,
119119             tpl;
119120             
119121         me.callParent(arguments);
119122         tpl = me.tpl = (!Ext.isPrimitive(me.tpl) && me.tpl.compile) ? me.tpl : Ext.create('Ext.XTemplate', me.tpl);
119123
119124         me.renderer = function(value, p, record) {
119125             var data = Ext.apply({}, record.data, record.getAssociatedData());
119126             return tpl.apply(data);
119127         };
119128     }
119129 });
119130
119131 /**
119132  * @class Ext.grid.feature.Feature
119133  * @extends Ext.util.Observable
119134  * 
119135  * A feature is a type of plugin that is specific to the {@link Ext.grid.Panel}. It provides several
119136  * hooks that allows the developer to inject additional functionality at certain points throughout the 
119137  * grid creation cycle. This class provides the base template methods that are available to the developer,
119138  * it should be extended.
119139  * 
119140  * There are several built in features that extend this class, for example:
119141  *
119142  *  - {@link Ext.grid.feature.Grouping} - Shows grid rows in groups as specified by the {@link Ext.data.Store}
119143  *  - {@link Ext.grid.feature.RowBody} - Adds a body section for each grid row that can contain markup.
119144  *  - {@link Ext.grid.feature.Summary} - Adds a summary row at the bottom of the grid with aggregate totals for a column.
119145  * 
119146  * ## Using Features
119147  * A feature is added to the grid by specifying it an array of features in the configuration:
119148  * 
119149  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping');
119150  *     Ext.create('Ext.grid.Panel', {
119151  *         // other options
119152  *         features: [groupingFeature]
119153  *     });
119154  * 
119155  * @abstract
119156  */
119157 Ext.define('Ext.grid.feature.Feature', {
119158     extend: 'Ext.util.Observable',
119159     alias: 'feature.feature',
119160     
119161     isFeature: true,
119162     disabled: false,
119163     
119164     /**
119165      * @property {Boolean}
119166      * Most features will expose additional events, some may not and will
119167      * need to change this to false.
119168      */
119169     hasFeatureEvent: true,
119170     
119171     /**
119172      * @property {String}
119173      * Prefix to use when firing events on the view.
119174      * For example a prefix of group would expose "groupclick", "groupcontextmenu", "groupdblclick".
119175      */
119176     eventPrefix: null,
119177     
119178     /**
119179      * @property {String}
119180      * Selector used to determine when to fire the event with the eventPrefix.
119181      */
119182     eventSelector: null,
119183     
119184     /**
119185      * @property {Ext.view.Table}
119186      * Reference to the TableView.
119187      */
119188     view: null,
119189     
119190     /**
119191      * @property {Ext.grid.Panel}
119192      * Reference to the grid panel
119193      */
119194     grid: null,
119195     
119196     /**
119197      * Most features will not modify the data returned to the view.
119198      * This is limited to one feature that manipulates the data per grid view.
119199      */
119200     collectData: false,
119201         
119202     getFeatureTpl: function() {
119203         return '';
119204     },
119205     
119206     /**
119207      * Abstract method to be overriden when a feature should add additional
119208      * arguments to its event signature. By default the event will fire:
119209      * - view - The underlying Ext.view.Table
119210      * - featureTarget - The matched element by the defined {@link #eventSelector}
119211      *
119212      * The method must also return the eventName as the first index of the array
119213      * to be passed to fireEvent.
119214      * @template
119215      */
119216     getFireEventArgs: function(eventName, view, featureTarget, e) {
119217         return [eventName, view, featureTarget, e];
119218     },
119219     
119220     /**
119221      * Approriate place to attach events to the view, selectionmodel, headerCt, etc
119222      * @template
119223      */
119224     attachEvents: function() {
119225         
119226     },
119227     
119228     getFragmentTpl: function() {
119229         return;
119230     },
119231     
119232     /**
119233      * Allows a feature to mutate the metaRowTpl.
119234      * The array received as a single argument can be manipulated to add things
119235      * on the end/begining of a particular row.
119236      * @template
119237      */
119238     mutateMetaRowTpl: function(metaRowTplArray) {
119239         
119240     },
119241     
119242     /**
119243      * Allows a feature to inject member methods into the metaRowTpl. This is
119244      * important for embedding functionality which will become part of the proper
119245      * row tpl.
119246      * @template
119247      */
119248     getMetaRowTplFragments: function() {
119249         return {};
119250     },
119251
119252     getTableFragments: function() {
119253         return {};
119254     },
119255     
119256     /**
119257      * Provide additional data to the prepareData call within the grid view.
119258      * @param {Object} data The data for this particular record.
119259      * @param {Number} idx The row index for this record.
119260      * @param {Ext.data.Model} record The record instance
119261      * @param {Object} orig The original result from the prepareData call to massage.
119262      * @template
119263      */
119264     getAdditionalData: function(data, idx, record, orig) {
119265         return {};
119266     },
119267     
119268     /**
119269      * Enable a feature
119270      */
119271     enable: function() {
119272         this.disabled = false;
119273     },
119274     
119275     /**
119276      * Disable a feature
119277      */
119278     disable: function() {
119279         this.disabled = true;
119280     }
119281     
119282 });
119283 /**
119284  * @class Ext.grid.feature.AbstractSummary
119285  * @extends Ext.grid.feature.Feature
119286  * A small abstract class that contains the shared behaviour for any summary
119287  * calculations to be used in the grid.
119288  */
119289 Ext.define('Ext.grid.feature.AbstractSummary', {
119290     
119291     /* Begin Definitions */
119292    
119293     extend: 'Ext.grid.feature.Feature',
119294     
119295     alias: 'feature.abstractsummary',
119296    
119297     /* End Definitions */
119298    
119299    /**
119300     * @cfg {Boolean} showSummaryRow True to show the summary row. Defaults to <tt>true</tt>.
119301     */
119302     showSummaryRow: true,
119303     
119304     // @private
119305     nestedIdRe: /\{\{id\}([\w\-]*)\}/g,
119306     
119307     /**
119308      * Toggle whether or not to show the summary row.
119309      * @param {Boolean} visible True to show the summary row
119310      */
119311     toggleSummaryRow: function(visible){
119312         this.showSummaryRow = !!visible;
119313     },
119314     
119315     /**
119316      * Gets any fragments to be used in the tpl
119317      * @private
119318      * @return {Object} The fragments
119319      */
119320     getSummaryFragments: function(){
119321         var fragments = {};
119322         if (this.showSummaryRow) {
119323             Ext.apply(fragments, {
119324                 printSummaryRow: Ext.bind(this.printSummaryRow, this)
119325             });
119326         }
119327         return fragments;
119328     },
119329     
119330     /**
119331      * Prints a summary row
119332      * @private
119333      * @param {Object} index The index in the template
119334      * @return {String} The value of the summary row
119335      */
119336     printSummaryRow: function(index){
119337         var inner = this.view.getTableChunker().metaRowTpl.join(''),
119338             prefix = Ext.baseCSSPrefix;
119339         
119340         inner = inner.replace(prefix + 'grid-row', prefix + 'grid-row-summary');
119341         inner = inner.replace('{{id}}', '{gridSummaryValue}');
119342         inner = inner.replace(this.nestedIdRe, '{id$1}');  
119343         inner = inner.replace('{[this.embedRowCls()]}', '{rowCls}');
119344         inner = inner.replace('{[this.embedRowAttr()]}', '{rowAttr}');
119345         inner = Ext.create('Ext.XTemplate', inner, {
119346             firstOrLastCls: Ext.view.TableChunker.firstOrLastCls
119347         });
119348         
119349         return inner.applyTemplate({
119350             columns: this.getPrintData(index)
119351         });
119352     },
119353     
119354     /**
119355      * Gets the value for the column from the attached data.
119356      * @param {Ext.grid.column.Column} column The header
119357      * @param {Object} data The current data
119358      * @return {String} The value to be rendered
119359      */
119360     getColumnValue: function(column, summaryData){
119361         var comp     = Ext.getCmp(column.id),
119362             value    = summaryData[column.id],
119363             renderer = comp.summaryRenderer;
119364
119365         if (renderer) {
119366             value = renderer.call(
119367                 comp.scope || this,
119368                 value,
119369                 summaryData,
119370                 column.dataIndex
119371             );
119372         }
119373         return value;
119374     },
119375     
119376     /**
119377      * Get the summary data for a field.
119378      * @private
119379      * @param {Ext.data.Store} store The store to get the data from
119380      * @param {String/Function} type The type of aggregation. If a function is specified it will
119381      * be passed to the stores aggregate function.
119382      * @param {String} field The field to aggregate on
119383      * @param {Boolean} group True to aggregate in grouped mode 
119384      * @return {Number/String/Object} See the return type for the store functions.
119385      */
119386     getSummary: function(store, type, field, group){
119387         if (type) {
119388             if (Ext.isFunction(type)) {
119389                 return store.aggregate(type, null, group);
119390             }
119391             
119392             switch (type) {
119393                 case 'count':
119394                     return store.count(group);
119395                 case 'min':
119396                     return store.min(field, group);
119397                 case 'max':
119398                     return store.max(field, group);
119399                 case 'sum':
119400                     return store.sum(field, group);
119401                 case 'average':
119402                     return store.average(field, group);
119403                 default:
119404                     return group ? {} : '';
119405                     
119406             }
119407         }
119408     }
119409     
119410 });
119411
119412 /**
119413  * @class Ext.grid.feature.Chunking
119414  * @extends Ext.grid.feature.Feature
119415  */
119416 Ext.define('Ext.grid.feature.Chunking', {
119417     extend: 'Ext.grid.feature.Feature',
119418     alias: 'feature.chunking',
119419     
119420     chunkSize: 20,
119421     rowHeight: Ext.isIE ? 27 : 26,
119422     visibleChunk: 0,
119423     hasFeatureEvent: false,
119424     attachEvents: function() {
119425         var grid = this.view.up('gridpanel'),
119426             scroller = grid.down('gridscroller[dock=right]');
119427         scroller.el.on('scroll', this.onBodyScroll, this, {buffer: 300});
119428         //this.view.on('bodyscroll', this.onBodyScroll, this, {buffer: 300});
119429     },
119430     
119431     onBodyScroll: function(e, t) {
119432         var view = this.view,
119433             top  = t.scrollTop,
119434             nextChunk = Math.floor(top / this.rowHeight / this.chunkSize);
119435         if (nextChunk !== this.visibleChunk) {
119436         
119437             this.visibleChunk = nextChunk;
119438             view.refresh();
119439             view.el.dom.scrollTop = top;
119440             //BrowserBug: IE6,7,8 quirks mode takes setting scrollTop 2x.
119441             view.el.dom.scrollTop = top;
119442         }
119443     },
119444     
119445     collectData: function(records, preppedRecords, startIndex, fullWidth, orig) {
119446         var o = {
119447             fullWidth: orig.fullWidth,
119448             chunks: []
119449         },
119450         //headerCt = this.view.headerCt,
119451         //colums = headerCt.getColumnsForTpl(),
119452         recordCount = orig.rows.length,
119453         start = 0,
119454         i = 0,
119455         visibleChunk = this.visibleChunk,
119456         chunk,
119457         rows,
119458         chunkLength;
119459
119460         for (; start < recordCount; start+=this.chunkSize, i++) {
119461             if (start+this.chunkSize > recordCount) {
119462                 chunkLength = recordCount - start;
119463             } else {
119464                 chunkLength = this.chunkSize;
119465             }
119466             
119467             if (i >= visibleChunk - 1 && i <= visibleChunk + 1) {
119468                 rows = orig.rows.slice(start, start+this.chunkSize);
119469             } else {
119470                 rows = [];
119471             }
119472             o.chunks.push({
119473                 rows: rows,
119474                 fullWidth: fullWidth,
119475                 chunkHeight: chunkLength * this.rowHeight
119476             });
119477         }
119478         
119479         
119480         return o;
119481     },
119482     
119483     getTableFragments: function() {
119484         return {
119485             openTableWrap: function() {
119486                 return '<tpl for="chunks"><div class="' + Ext.baseCSSPrefix + 'grid-chunk" style="height: {chunkHeight}px;">';
119487             },
119488             closeTableWrap: function() {
119489                 return '</div></tpl>';
119490             }
119491         };
119492     }
119493 });
119494
119495 /**
119496  * @class Ext.grid.feature.Grouping
119497  * @extends Ext.grid.feature.Feature
119498  * 
119499  * This feature allows to display the grid rows aggregated into groups as specified by the {@link Ext.data.Store#groupers}
119500  * specified on the Store. The group will show the title for the group name and then the appropriate records for the group
119501  * underneath. The groups can also be expanded and collapsed.
119502  * 
119503  * ## Extra Events
119504  * This feature adds several extra events that will be fired on the grid to interact with the groups:
119505  *
119506  *  - {@link #groupclick}
119507  *  - {@link #groupdblclick}
119508  *  - {@link #groupcontextmenu}
119509  *  - {@link #groupexpand}
119510  *  - {@link #groupcollapse}
119511  * 
119512  * ## Menu Augmentation
119513  * This feature adds extra options to the grid column menu to provide the user with functionality to modify the grouping.
119514  * This can be disabled by setting the {@link #enableGroupingMenu} option. The option to disallow grouping from being turned off
119515  * by thew user is {@link #enableNoGroups}.
119516  * 
119517  * ## Controlling Group Text
119518  * The {@link #groupHeaderTpl} is used to control the rendered title for each group. It can modified to customized
119519  * the default display.
119520  * 
119521  * ## Example Usage
119522  * 
119523  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
119524  *         groupHeaderTpl: 'Group: {name} ({rows.length})', //print the number of items in the group
119525  *         startCollapsed: true // start all groups collapsed
119526  *     });
119527  * 
119528  * @ftype grouping
119529  * @author Nicolas Ferrero
119530  */
119531 Ext.define('Ext.grid.feature.Grouping', {
119532     extend: 'Ext.grid.feature.Feature',
119533     alias: 'feature.grouping',
119534
119535     eventPrefix: 'group',
119536     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
119537
119538     constructor: function() {
119539         var me = this;
119540         
119541         me.collapsedState = {};
119542         me.callParent(arguments);
119543     },
119544     
119545     /**
119546      * @event groupclick
119547      * @param {Ext.view.Table} view
119548      * @param {HTMLElement} node
119549      * @param {String} group The name of the group
119550      * @param {Ext.EventObject} e
119551      */
119552
119553     /**
119554      * @event groupdblclick
119555      * @param {Ext.view.Table} view
119556      * @param {HTMLElement} node
119557      * @param {String} group The name of the group
119558      * @param {Ext.EventObject} e
119559      */
119560
119561     /**
119562      * @event groupcontextmenu
119563      * @param {Ext.view.Table} view
119564      * @param {HTMLElement} node
119565      * @param {String} group The name of the group
119566      * @param {Ext.EventObject} e
119567      */
119568
119569     /**
119570      * @event groupcollapse
119571      * @param {Ext.view.Table} view
119572      * @param {HTMLElement} node
119573      * @param {String} group The name of the group
119574      * @param {Ext.EventObject} e
119575      */
119576
119577     /**
119578      * @event groupexpand
119579      * @param {Ext.view.Table} view
119580      * @param {HTMLElement} node
119581      * @param {String} group The name of the group
119582      * @param {Ext.EventObject} e
119583      */
119584
119585     /**
119586      * @cfg {String} groupHeaderTpl
119587      * Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
119588      * Defaults to 'Group: {name}'
119589      */
119590     groupHeaderTpl: 'Group: {name}',
119591
119592     /**
119593      * @cfg {Number} depthToIndent
119594      * Number of pixels to indent per grouping level
119595      */
119596     depthToIndent: 17,
119597
119598     collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
119599     hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
119600
119601     /**
119602      * @cfg {String} groupByText Text displayed in the grid header menu for grouping by header.
119603      */
119604     groupByText : 'Group By This Field',
119605     /**
119606      * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping.
119607      */
119608     showGroupsText : 'Show in Groups',
119609
119610     /**
119611      * @cfg {Boolean} hideGroupedHeader<tt>true</tt> to hide the header that is currently grouped.
119612      */
119613     hideGroupedHeader : false,
119614
119615     /**
119616      * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed
119617      */
119618     startCollapsed : false,
119619
119620     /**
119621      * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the header menu
119622      */
119623     enableGroupingMenu : true,
119624
119625     /**
119626      * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping
119627      */
119628     enableNoGroups : true,
119629     
119630     enable: function() {
119631         var me    = this,
119632             view  = me.view,
119633             store = view.store,
119634             groupToggleMenuItem;
119635             
119636         me.lastGroupField = me.getGroupField();
119637
119638         if (me.lastGroupIndex) {
119639             store.group(me.lastGroupIndex);
119640         }
119641         me.callParent();
119642         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
119643         groupToggleMenuItem.setChecked(true, true);
119644         me.refreshIf();
119645     },
119646
119647     disable: function() {
119648         var me    = this,
119649             view  = me.view,
119650             store = view.store,
119651             remote = store.remoteGroup,
119652             groupToggleMenuItem,
119653             lastGroup;
119654             
119655         lastGroup = store.groupers.first();
119656         if (lastGroup) {
119657             me.lastGroupIndex = lastGroup.property;
119658             me.block();
119659             store.clearGrouping();
119660             me.unblock();
119661         }
119662         
119663         me.callParent();
119664         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
119665         groupToggleMenuItem.setChecked(true, true);
119666         groupToggleMenuItem.setChecked(false, true);
119667         if (!remote) {
119668             view.refresh();
119669         }
119670     },
119671     
119672     refreshIf: function() {
119673         if (this.blockRefresh !== true) {
119674             this.view.refresh();
119675         }    
119676     },
119677
119678     getFeatureTpl: function(values, parent, x, xcount) {
119679         var me = this;
119680         
119681         return [
119682             '<tpl if="typeof rows !== \'undefined\'">',
119683                 // group row tpl
119684                 '<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>',
119685                 // this is the rowbody
119686                 '<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>',
119687             '</tpl>'
119688         ].join('');
119689     },
119690
119691     getFragmentTpl: function() {
119692         return {
119693             indentByDepth: this.indentByDepth,
119694             depthToIndent: this.depthToIndent
119695         };
119696     },
119697
119698     indentByDepth: function(values) {
119699         var depth = values.depth || 0;
119700         return 'style="padding-left:'+ depth * this.depthToIndent + 'px;"';
119701     },
119702
119703     // Containers holding these components are responsible for
119704     // destroying them, we are just deleting references.
119705     destroy: function() {
119706         var me = this;
119707         
119708         delete me.view;
119709         delete me.prunedHeader;
119710     },
119711
119712     // perhaps rename to afterViewRender
119713     attachEvents: function() {
119714         var me = this,
119715             view = me.view;
119716
119717         view.on({
119718             scope: me,
119719             groupclick: me.onGroupClick,
119720             rowfocus: me.onRowFocus
119721         });
119722         view.store.on('groupchange', me.onGroupChange, me);
119723
119724         me.pruneGroupedHeader();
119725
119726         if (me.enableGroupingMenu) {
119727             me.injectGroupingMenu();
119728         }
119729         me.lastGroupField = me.getGroupField();
119730         me.block();
119731         me.onGroupChange();
119732         me.unblock();
119733     },
119734     
119735     injectGroupingMenu: function() {
119736         var me       = this,
119737             view     = me.view,
119738             headerCt = view.headerCt;
119739         headerCt.showMenuBy = me.showMenuBy;
119740         headerCt.getMenuItems = me.getMenuItems();
119741     },
119742     
119743     showMenuBy: function(t, header) {
119744         var menu = this.getMenu(),
119745             groupMenuItem  = menu.down('#groupMenuItem'),
119746             groupableMth = header.groupable === false ?  'disable' : 'enable';
119747             
119748         groupMenuItem[groupableMth]();
119749         Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
119750     },
119751     
119752     getMenuItems: function() {
119753         var me                 = this,
119754             groupByText        = me.groupByText,
119755             disabled           = me.disabled,
119756             showGroupsText     = me.showGroupsText,
119757             enableNoGroups     = me.enableNoGroups,
119758             groupMenuItemClick = Ext.Function.bind(me.onGroupMenuItemClick, me),
119759             groupToggleMenuItemClick = Ext.Function.bind(me.onGroupToggleMenuItemClick, me);
119760         
119761         // runs in the scope of headerCt
119762         return function() {
119763             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
119764             o.push('-', {
119765                 iconCls: Ext.baseCSSPrefix + 'group-by-icon',
119766                 itemId: 'groupMenuItem',
119767                 text: groupByText,
119768                 handler: groupMenuItemClick
119769             });
119770             if (enableNoGroups) {
119771                 o.push({
119772                     itemId: 'groupToggleMenuItem',
119773                     text: showGroupsText,
119774                     checked: !disabled,
119775                     checkHandler: groupToggleMenuItemClick
119776                 });
119777             }
119778             return o;
119779         };
119780     },
119781
119782
119783     /**
119784      * Group by the header the user has clicked on.
119785      * @private
119786      */
119787     onGroupMenuItemClick: function(menuItem, e) {
119788         var me = this,
119789             menu = menuItem.parentMenu,
119790             hdr  = menu.activeHeader,
119791             view = me.view,
119792             store = view.store,
119793             remote = store.remoteGroup;
119794
119795         delete me.lastGroupIndex;
119796         me.block();
119797         me.enable();
119798         store.group(hdr.dataIndex);
119799         me.pruneGroupedHeader();
119800         me.unblock();
119801         if (!remote) {
119802             view.refresh();
119803         }  
119804     },
119805     
119806     block: function(){
119807         this.blockRefresh = this.view.blockRefresh = true;
119808     },
119809     
119810     unblock: function(){
119811         this.blockRefresh = this.view.blockRefresh = false;
119812     },
119813
119814     /**
119815      * Turn on and off grouping via the menu
119816      * @private
119817      */
119818     onGroupToggleMenuItemClick: function(menuItem, checked) {
119819         this[checked ? 'enable' : 'disable']();
119820     },
119821
119822     /**
119823      * Prunes the grouped header from the header container
119824      * @private
119825      */
119826     pruneGroupedHeader: function() {
119827         var me         = this,
119828             view       = me.view,
119829             store      = view.store,
119830             groupField = me.getGroupField(),
119831             headerCt   = view.headerCt,
119832             header     = headerCt.down('header[dataIndex=' + groupField + ']');
119833
119834         if (header) {
119835             if (me.prunedHeader) {
119836                 me.prunedHeader.show();
119837             }
119838             me.prunedHeader = header;
119839             header.hide();
119840         }
119841     },
119842
119843     getGroupField: function(){
119844         var group = this.view.store.groupers.first();
119845         if (group) {
119846             return group.property;    
119847         }
119848         return ''; 
119849     },
119850
119851     /**
119852      * When a row gains focus, expand the groups above it
119853      * @private
119854      */
119855     onRowFocus: function(rowIdx) {
119856         var node    = this.view.getNode(rowIdx),
119857             groupBd = Ext.fly(node).up('.' + this.collapsedCls);
119858
119859         if (groupBd) {
119860             // for multiple level groups, should expand every groupBd
119861             // above
119862             this.expand(groupBd);
119863         }
119864     },
119865
119866     /**
119867      * Expand a group by the groupBody
119868      * @param {Ext.Element} groupBd
119869      * @private
119870      */
119871     expand: 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] = false;
119878
119879         groupBd.removeCls(me.collapsedCls);
119880         groupBd.prev().removeCls(me.hdCollapsedCls);
119881
119882         grid.determineScrollbars();
119883         grid.invalidateScroller();
119884         view.fireEvent('groupexpand');
119885     },
119886
119887     /**
119888      * Collapse a group by the groupBody
119889      * @param {Ext.Element} groupBd
119890      * @private
119891      */
119892     collapse: function(groupBd) {
119893         var me = this,
119894             view = me.view,
119895             grid = view.up('gridpanel'),
119896             groupBdDom = Ext.getDom(groupBd);
119897             
119898         me.collapsedState[groupBdDom.id] = true;
119899
119900         groupBd.addCls(me.collapsedCls);
119901         groupBd.prev().addCls(me.hdCollapsedCls);
119902
119903         grid.determineScrollbars();
119904         grid.invalidateScroller();
119905         view.fireEvent('groupcollapse');
119906     },
119907     
119908     onGroupChange: function(){
119909         var me = this,
119910             field = me.getGroupField(),
119911             menuItem;
119912             
119913         if (me.hideGroupedHeader) {
119914             if (me.lastGroupField) {
119915                 menuItem = me.getMenuItem(me.lastGroupField);
119916                 if (menuItem) {
119917                     menuItem.setChecked(true);
119918                 }
119919             }
119920             if (field) {
119921                 menuItem = me.getMenuItem(field);
119922                 if (menuItem) {
119923                     menuItem.setChecked(false);
119924                 }
119925             }
119926         }
119927         if (me.blockRefresh !== true) {
119928             me.view.refresh();
119929         }
119930         me.lastGroupField = field;
119931     },
119932     
119933     /**
119934      * Gets the related menu item for a dataIndex
119935      * @private
119936      * @return {Ext.grid.header.Container} The header
119937      */
119938     getMenuItem: function(dataIndex){
119939         var view = this.view,
119940             header = view.headerCt.down('gridcolumn[dataIndex=' + dataIndex + ']'),
119941             menu = view.headerCt.getMenu();
119942             
119943         return menu.down('menuitem[headerId='+ header.id +']');
119944     },
119945
119946     /**
119947      * Toggle between expanded/collapsed state when clicking on
119948      * the group.
119949      * @private
119950      */
119951     onGroupClick: function(view, group, idx, foo, e) {
119952         var me = this,
119953             toggleCls = me.toggleCls,
119954             groupBd = Ext.fly(group.nextSibling, '_grouping');
119955
119956         if (groupBd.hasCls(me.collapsedCls)) {
119957             me.expand(groupBd);
119958         } else {
119959             me.collapse(groupBd);
119960         }
119961     },
119962
119963     // Injects isRow and closeRow into the metaRowTpl.
119964     getMetaRowTplFragments: function() {
119965         return {
119966             isRow: this.isRow,
119967             closeRow: this.closeRow
119968         };
119969     },
119970
119971     // injected into rowtpl and wrapped around metaRowTpl
119972     // becomes part of the standard tpl
119973     isRow: function() {
119974         return '<tpl if="typeof rows === \'undefined\'">';
119975     },
119976
119977     // injected into rowtpl and wrapped around metaRowTpl
119978     // becomes part of the standard tpl
119979     closeRow: function() {
119980         return '</tpl>';
119981     },
119982
119983     // isRow and closeRow are injected via getMetaRowTplFragments
119984     mutateMetaRowTpl: function(metaRowTpl) {
119985         metaRowTpl.unshift('{[this.isRow()]}');
119986         metaRowTpl.push('{[this.closeRow()]}');
119987     },
119988
119989     // injects an additional style attribute via tdAttrKey with the proper
119990     // amount of padding
119991     getAdditionalData: function(data, idx, record, orig) {
119992         var view = this.view,
119993             hCt  = view.headerCt,
119994             col  = hCt.items.getAt(0),
119995             o = {},
119996             tdAttrKey = col.id + '-tdAttr';
119997
119998         // maintain the current tdAttr that a user may ahve set.
119999         o[tdAttrKey] = this.indentByDepth(data) + " " + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
120000         o.collapsed = 'true';
120001         return o;
120002     },
120003
120004     // return matching preppedRecords
120005     getGroupRows: function(group, records, preppedRecords, fullWidth) {
120006         var me = this,
120007             children = group.children,
120008             rows = group.rows = [],
120009             view = me.view;
120010         group.viewId = view.id;
120011
120012         Ext.Array.each(records, function(record, idx) {
120013             if (Ext.Array.indexOf(children, record) != -1) {
120014                 rows.push(Ext.apply(preppedRecords[idx], {
120015                     depth: 1
120016                 }));
120017             }
120018         });
120019         delete group.children;
120020         group.fullWidth = fullWidth;
120021         if (me.collapsedState[view.id + '-gp-' + group.name]) {
120022             group.collapsedCls = me.collapsedCls;
120023             group.hdCollapsedCls = me.hdCollapsedCls;
120024         }
120025
120026         return group;
120027     },
120028
120029     // return the data in a grouped format.
120030     collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
120031         var me    = this,
120032             store = me.view.store,
120033             groups;
120034             
120035         if (!me.disabled && store.isGrouped()) {
120036             groups = store.getGroups();
120037             Ext.Array.each(groups, function(group, idx){
120038                 me.getGroupRows(group, records, preppedRecords, fullWidth);
120039             }, me);
120040             return {
120041                 rows: groups,
120042                 fullWidth: fullWidth
120043             };
120044         }
120045         return o;
120046     },
120047     
120048     // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
120049     // events that are fired on the view. Chose not to return the actual
120050     // group itself because of its expense and because developers can simply
120051     // grab the group via store.getGroups(groupName)
120052     getFireEventArgs: function(type, view, featureTarget, e) {
120053         var returnArray = [type, view, featureTarget],
120054             groupBd     = Ext.fly(featureTarget.nextSibling, '_grouping'),
120055             groupBdId   = Ext.getDom(groupBd).id,
120056             prefix      = view.id + '-gp-',
120057             groupName   = groupBdId.substr(prefix.length);
120058         
120059         returnArray.push(groupName, e);
120060         
120061         return returnArray;
120062     }
120063 });
120064
120065 /**
120066  * @class Ext.grid.feature.GroupingSummary
120067  * @extends Ext.grid.feature.Grouping
120068  *
120069  * This feature adds an aggregate summary row at the bottom of each group that is provided
120070  * by the {@link Ext.grid.feature.Grouping} feature. There are two aspects to the summary:
120071  *
120072  * ## Calculation
120073  *
120074  * The summary value needs to be calculated for each column in the grid. This is controlled
120075  * by the summaryType option specified on the column. There are several built in summary types,
120076  * which can be specified as a string on the column configuration. These call underlying methods
120077  * on the store:
120078  *
120079  *  - {@link Ext.data.Store#count count}
120080  *  - {@link Ext.data.Store#sum sum}
120081  *  - {@link Ext.data.Store#min min}
120082  *  - {@link Ext.data.Store#max max}
120083  *  - {@link Ext.data.Store#average average}
120084  *
120085  * Alternatively, the summaryType can be a function definition. If this is the case,
120086  * the function is called with an array of records to calculate the summary value.
120087  *
120088  * ## Rendering
120089  *
120090  * Similar to a column, the summary also supports a summaryRenderer function. This
120091  * summaryRenderer is called before displaying a value. The function is optional, if
120092  * not specified the default calculated value is shown. The summaryRenderer is called with:
120093  *
120094  *  - value {Object} - The calculated value.
120095  *  - summaryData {Object} - Contains all raw summary values for the row.
120096  *  - field {String} - The name of the field we are calculating
120097  *
120098  * ## Example Usage
120099  *
120100  *     @example
120101  *     Ext.define('TestResult', {
120102  *         extend: 'Ext.data.Model',
120103  *         fields: ['student', 'subject', {
120104  *             name: 'mark',
120105  *             type: 'int'
120106  *         }]
120107  *     });
120108  *
120109  *     Ext.create('Ext.grid.Panel', {
120110  *         width: 200,
120111  *         height: 240,
120112  *         renderTo: document.body,
120113  *         features: [{
120114  *             groupHeaderTpl: 'Subject: {name}',
120115  *             ftype: 'groupingsummary'
120116  *         }],
120117  *         store: {
120118  *             model: 'TestResult',
120119  *             groupField: 'subject',
120120  *             data: [{
120121  *                 student: 'Student 1',
120122  *                 subject: 'Math',
120123  *                 mark: 84
120124  *             },{
120125  *                 student: 'Student 1',
120126  *                 subject: 'Science',
120127  *                 mark: 72
120128  *             },{
120129  *                 student: 'Student 2',
120130  *                 subject: 'Math',
120131  *                 mark: 96
120132  *             },{
120133  *                 student: 'Student 2',
120134  *                 subject: 'Science',
120135  *                 mark: 68
120136  *             }]
120137  *         },
120138  *         columns: [{
120139  *             dataIndex: 'student',
120140  *             text: 'Name',
120141  *             summaryType: 'count',
120142  *             summaryRenderer: function(value){
120143  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : '');
120144  *             }
120145  *         }, {
120146  *             dataIndex: 'mark',
120147  *             text: 'Mark',
120148  *             summaryType: 'average'
120149  *         }]
120150  *     });
120151  */
120152 Ext.define('Ext.grid.feature.GroupingSummary', {
120153
120154     /* Begin Definitions */
120155
120156     extend: 'Ext.grid.feature.Grouping',
120157
120158     alias: 'feature.groupingsummary',
120159
120160     mixins: {
120161         summary: 'Ext.grid.feature.AbstractSummary'
120162     },
120163
120164     /* End Definitions */
120165
120166
120167    /**
120168     * Modifies the row template to include the summary row.
120169     * @private
120170     * @return {String} The modified template
120171     */
120172    getFeatureTpl: function() {
120173         var tpl = this.callParent(arguments);
120174
120175         if (this.showSummaryRow) {
120176             // lop off the end </tpl> so we can attach it
120177             tpl = tpl.replace('</tpl>', '');
120178             tpl += '{[this.printSummaryRow(xindex)]}</tpl>';
120179         }
120180         return tpl;
120181     },
120182
120183     /**
120184      * Gets any fragments needed for the template.
120185      * @private
120186      * @return {Object} The fragments
120187      */
120188     getFragmentTpl: function() {
120189         var me = this,
120190             fragments = me.callParent();
120191
120192         Ext.apply(fragments, me.getSummaryFragments());
120193         if (me.showSummaryRow) {
120194             // this gets called before render, so we'll setup the data here.
120195             me.summaryGroups = me.view.store.getGroups();
120196             me.summaryData = me.generateSummaryData();
120197         }
120198         return fragments;
120199     },
120200
120201     /**
120202      * Gets the data for printing a template row
120203      * @private
120204      * @param {Number} index The index in the template
120205      * @return {Array} The template values
120206      */
120207     getPrintData: function(index){
120208         var me = this,
120209             columns = me.view.headerCt.getColumnsForTpl(),
120210             i = 0,
120211             length = columns.length,
120212             data = [],
120213             name = me.summaryGroups[index - 1].name,
120214             active = me.summaryData[name],
120215             column;
120216
120217         for (; i < length; ++i) {
120218             column = columns[i];
120219             column.gridSummaryValue = this.getColumnValue(column, active);
120220             data.push(column);
120221         }
120222         return data;
120223     },
120224
120225     /**
120226      * Generates all of the summary data to be used when processing the template
120227      * @private
120228      * @return {Object} The summary data
120229      */
120230     generateSummaryData: function(){
120231         var me = this,
120232             data = {},
120233             remoteData = {},
120234             store = me.view.store,
120235             groupField = this.getGroupField(),
120236             reader = store.proxy.reader,
120237             groups = me.summaryGroups,
120238             columns = me.view.headerCt.getColumnsForTpl(),
120239             remote,
120240             i,
120241             length,
120242             fieldData,
120243             root,
120244             key,
120245             comp;
120246
120247         for (i = 0, length = groups.length; i < length; ++i) {
120248             data[groups[i].name] = {};
120249         }
120250
120251         /**
120252          * @cfg {String} [remoteRoot=undefined]  The name of the property which contains the Array of
120253          * summary objects. It allows to use server-side calculated summaries.
120254          */
120255         if (me.remoteRoot && reader.rawData) {
120256             // reset reader root and rebuild extractors to extract summaries data
120257             root = reader.root;
120258             reader.root = me.remoteRoot;
120259             reader.buildExtractors(true);
120260             Ext.Array.each(reader.getRoot(reader.rawData), function(value) {
120261                  remoteData[value[groupField]] = value;
120262             });
120263             // restore initial reader configuration
120264             reader.root = root;
120265             reader.buildExtractors(true);
120266         }
120267
120268         for (i = 0, length = columns.length; i < length; ++i) {
120269             comp = Ext.getCmp(columns[i].id);
120270             fieldData = me.getSummary(store, comp.summaryType, comp.dataIndex, true);
120271
120272             for (key in fieldData) {
120273                 if (fieldData.hasOwnProperty(key)) {
120274                     data[key][comp.id] = fieldData[key];
120275                 }
120276             }
120277
120278             for (key in remoteData) {
120279                 if (remoteData.hasOwnProperty(key)) {
120280                     remote = remoteData[key][comp.dataIndex];
120281                     if (remote !== undefined && data[key] !== undefined) {
120282                         data[key][comp.id] = remote;
120283                     }
120284                 }
120285             }
120286         }
120287         return data;
120288     }
120289 });
120290
120291 /**
120292  * @class Ext.grid.feature.RowBody
120293  * @extends Ext.grid.feature.Feature
120294  *
120295  * The rowbody feature enhances the grid's markup to have an additional
120296  * tr -> td -> div which spans the entire width of the original row.
120297  *
120298  * This is useful to to associate additional information with a particular
120299  * record in a grid.
120300  *
120301  * Rowbodies are initially hidden unless you override getAdditionalData.
120302  *
120303  * Will expose additional events on the gridview with the prefix of 'rowbody'.
120304  * For example: 'rowbodyclick', 'rowbodydblclick', 'rowbodycontextmenu'.
120305  *
120306  * @ftype rowbody
120307  */
120308 Ext.define('Ext.grid.feature.RowBody', {
120309     extend: 'Ext.grid.feature.Feature',
120310     alias: 'feature.rowbody',
120311     rowBodyHiddenCls: Ext.baseCSSPrefix + 'grid-row-body-hidden',
120312     rowBodyTrCls: Ext.baseCSSPrefix + 'grid-rowbody-tr',
120313     rowBodyTdCls: Ext.baseCSSPrefix + 'grid-cell-rowbody',
120314     rowBodyDivCls: Ext.baseCSSPrefix + 'grid-rowbody',
120315
120316     eventPrefix: 'rowbody',
120317     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-rowbody-tr',
120318     
120319     getRowBody: function(values) {
120320         return [
120321             '<tr class="' + this.rowBodyTrCls + ' {rowBodyCls}">',
120322                 '<td class="' + this.rowBodyTdCls + '" colspan="{rowBodyColspan}">',
120323                     '<div class="' + this.rowBodyDivCls + '">{rowBody}</div>',
120324                 '</td>',
120325             '</tr>'
120326         ].join('');
120327     },
120328     
120329     // injects getRowBody into the metaRowTpl.
120330     getMetaRowTplFragments: function() {
120331         return {
120332             getRowBody: this.getRowBody,
120333             rowBodyTrCls: this.rowBodyTrCls,
120334             rowBodyTdCls: this.rowBodyTdCls,
120335             rowBodyDivCls: this.rowBodyDivCls
120336         };
120337     },
120338
120339     mutateMetaRowTpl: function(metaRowTpl) {
120340         metaRowTpl.push('{[this.getRowBody(values)]}');
120341     },
120342
120343     /**
120344      * Provide additional data to the prepareData call within the grid view.
120345      * The rowbody feature adds 3 additional variables into the grid view's template.
120346      * These are rowBodyCls, rowBodyColspan, and rowBody.
120347      * @param {Object} data The data for this particular record.
120348      * @param {Number} idx The row index for this record.
120349      * @param {Ext.data.Model} record The record instance
120350      * @param {Object} orig The original result from the prepareData call to massage.
120351      */
120352     getAdditionalData: function(data, idx, record, orig) {
120353         var headerCt = this.view.headerCt,
120354             colspan  = headerCt.getColumnCount();
120355
120356         return {
120357             rowBody: "",
120358             rowBodyCls: this.rowBodyCls,
120359             rowBodyColspan: colspan
120360         };
120361     }
120362 });
120363 /**
120364  * @class Ext.grid.feature.RowWrap
120365  * @extends Ext.grid.feature.Feature
120366  * @private
120367  */
120368 Ext.define('Ext.grid.feature.RowWrap', {
120369     extend: 'Ext.grid.feature.Feature',
120370     alias: 'feature.rowwrap',
120371
120372     // turn off feature events.
120373     hasFeatureEvent: false,
120374     
120375     mutateMetaRowTpl: function(metaRowTpl) {        
120376         // Remove "x-grid-row" from the first row, note this could be wrong
120377         // if some other feature unshifted things in front.
120378         metaRowTpl[0] = metaRowTpl[0].replace(Ext.baseCSSPrefix + 'grid-row', '');
120379         metaRowTpl[0] = metaRowTpl[0].replace("{[this.embedRowCls()]}", "");
120380         // 2
120381         metaRowTpl.unshift('<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" style="width: {[this.embedFullWidth()]}px;">');
120382         // 1
120383         metaRowTpl.unshift('<tr class="' + Ext.baseCSSPrefix + 'grid-row {[this.embedRowCls()]}"><td colspan="{[this.embedColSpan()]}"><div class="' + Ext.baseCSSPrefix + 'grid-rowwrap-div">');
120384         
120385         // 3
120386         metaRowTpl.push('</table>');
120387         // 4
120388         metaRowTpl.push('</div></td></tr>');
120389     },
120390     
120391     embedColSpan: function() {
120392         return '{colspan}';
120393     },
120394     
120395     embedFullWidth: function() {
120396         return '{fullWidth}';
120397     },
120398     
120399     getAdditionalData: function(data, idx, record, orig) {
120400         var headerCt = this.view.headerCt,
120401             colspan  = headerCt.getColumnCount(),
120402             fullWidth = headerCt.getFullWidth(),
120403             items    = headerCt.query('gridcolumn'),
120404             itemsLn  = items.length,
120405             i = 0,
120406             o = {
120407                 colspan: colspan,
120408                 fullWidth: fullWidth
120409             },
120410             id,
120411             tdClsKey,
120412             colResizerCls;
120413
120414         for (; i < itemsLn; i++) {
120415             id = items[i].id;
120416             tdClsKey = id + '-tdCls';
120417             colResizerCls = Ext.baseCSSPrefix + 'grid-col-resizer-'+id;
120418             // give the inner td's the resizer class
120419             // while maintaining anything a user may have injected via a custom
120420             // renderer
120421             o[tdClsKey] = colResizerCls + " " + (orig[tdClsKey] ? orig[tdClsKey] : '');
120422             // TODO: Unhackify the initial rendering width's
120423             o[id+'-tdAttr'] = " style=\"width: " + (items[i].hidden ? 0 : items[i].getDesiredWidth()) + "px;\" "/* + (i === 0 ? " rowspan=\"2\"" : "")*/;
120424             if (orig[id+'-tdAttr']) {
120425                 o[id+'-tdAttr'] += orig[id+'-tdAttr'];
120426             }
120427             
120428         }
120429
120430         return o;
120431     },
120432     
120433     getMetaRowTplFragments: function() {
120434         return {
120435             embedFullWidth: this.embedFullWidth,
120436             embedColSpan: this.embedColSpan
120437         };
120438     }
120439     
120440 });
120441 /**
120442  * @class Ext.grid.feature.Summary
120443  * @extends Ext.grid.feature.AbstractSummary
120444  * 
120445  * This feature is used to place a summary row at the bottom of the grid. If using a grouping, 
120446  * see {@link Ext.grid.feature.GroupingSummary}. There are 2 aspects to calculating the summaries, 
120447  * calculation and rendering.
120448  * 
120449  * ## Calculation
120450  * The summary value needs to be calculated for each column in the grid. This is controlled
120451  * by the summaryType option specified on the column. There are several built in summary types,
120452  * which can be specified as a string on the column configuration. These call underlying methods
120453  * on the store:
120454  *
120455  *  - {@link Ext.data.Store#count count}
120456  *  - {@link Ext.data.Store#sum sum}
120457  *  - {@link Ext.data.Store#min min}
120458  *  - {@link Ext.data.Store#max max}
120459  *  - {@link Ext.data.Store#average average}
120460  *
120461  * Alternatively, the summaryType can be a function definition. If this is the case,
120462  * the function is called with an array of records to calculate the summary value.
120463  * 
120464  * ## Rendering
120465  * Similar to a column, the summary also supports a summaryRenderer function. This
120466  * summaryRenderer is called before displaying a value. The function is optional, if
120467  * not specified the default calculated value is shown. The summaryRenderer is called with:
120468  *
120469  *  - value {Object} - The calculated value.
120470  *  - summaryData {Object} - Contains all raw summary values for the row.
120471  *  - field {String} - The name of the field we are calculating
120472  * 
120473  * ## Example Usage
120474  *
120475  *     @example
120476  *     Ext.define('TestResult', {
120477  *         extend: 'Ext.data.Model',
120478  *         fields: ['student', {
120479  *             name: 'mark',
120480  *             type: 'int'
120481  *         }]
120482  *     });
120483  *     
120484  *     Ext.create('Ext.grid.Panel', {
120485  *         width: 200,
120486  *         height: 140,
120487  *         renderTo: document.body,
120488  *         features: [{
120489  *             ftype: 'summary'
120490  *         }],
120491  *         store: {
120492  *             model: 'TestResult',
120493  *             data: [{
120494  *                 student: 'Student 1',
120495  *                 mark: 84
120496  *             },{
120497  *                 student: 'Student 2',
120498  *                 mark: 72
120499  *             },{
120500  *                 student: 'Student 3',
120501  *                 mark: 96
120502  *             },{
120503  *                 student: 'Student 4',
120504  *                 mark: 68
120505  *             }]
120506  *         },
120507  *         columns: [{
120508  *             dataIndex: 'student',
120509  *             text: 'Name',
120510  *             summaryType: 'count',
120511  *             summaryRenderer: function(value, summaryData, dataIndex) {
120512  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : ''); 
120513  *             }
120514  *         }, {
120515  *             dataIndex: 'mark',
120516  *             text: 'Mark',
120517  *             summaryType: 'average'
120518  *         }]
120519  *     });
120520  */
120521 Ext.define('Ext.grid.feature.Summary', {
120522     
120523     /* Begin Definitions */
120524     
120525     extend: 'Ext.grid.feature.AbstractSummary',
120526     
120527     alias: 'feature.summary',
120528     
120529     /* End Definitions */
120530     
120531     /**
120532      * Gets any fragments needed for the template.
120533      * @private
120534      * @return {Object} The fragments
120535      */
120536     getFragmentTpl: function() {
120537         // this gets called before render, so we'll setup the data here.
120538         this.summaryData = this.generateSummaryData(); 
120539         return this.getSummaryFragments();
120540     },
120541     
120542     /**
120543      * Overrides the closeRows method on the template so we can include our own custom
120544      * footer.
120545      * @private
120546      * @return {Object} The custom fragments
120547      */
120548     getTableFragments: function(){
120549         if (this.showSummaryRow) {
120550             return {
120551                 closeRows: this.closeRows
120552             };
120553         }
120554     },
120555     
120556     /**
120557      * Provide our own custom footer for the grid.
120558      * @private
120559      * @return {String} The custom footer
120560      */
120561     closeRows: function() {
120562         return '</tpl>{[this.printSummaryRow()]}';
120563     },
120564     
120565     /**
120566      * Gets the data for printing a template row
120567      * @private
120568      * @param {Number} index The index in the template
120569      * @return {Array} The template values
120570      */
120571     getPrintData: function(index){
120572         var me = this,
120573             columns = me.view.headerCt.getColumnsForTpl(),
120574             i = 0,
120575             length = columns.length,
120576             data = [],
120577             active = me.summaryData,
120578             column;
120579             
120580         for (; i < length; ++i) {
120581             column = columns[i];
120582             column.gridSummaryValue = this.getColumnValue(column, active);
120583             data.push(column);
120584         }
120585         return data;
120586     },
120587     
120588     /**
120589      * Generates all of the summary data to be used when processing the template
120590      * @private
120591      * @return {Object} The summary data
120592      */
120593     generateSummaryData: function(){
120594         var me = this,
120595             data = {},
120596             store = me.view.store,
120597             columns = me.view.headerCt.getColumnsForTpl(),
120598             i = 0,
120599             length = columns.length,
120600             fieldData,
120601             key,
120602             comp;
120603             
120604         for (i = 0, length = columns.length; i < length; ++i) {
120605             comp = Ext.getCmp(columns[i].id);
120606             data[comp.id] = me.getSummary(store, comp.summaryType, comp.dataIndex, false);
120607         }
120608         return data;
120609     }
120610 });
120611 /**
120612  * @class Ext.grid.header.DragZone
120613  * @extends Ext.dd.DragZone
120614  * @private
120615  */
120616 Ext.define('Ext.grid.header.DragZone', {
120617     extend: 'Ext.dd.DragZone',
120618     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
120619     maxProxyWidth: 120,
120620
120621     constructor: function(headerCt) {
120622         this.headerCt = headerCt;
120623         this.ddGroup =  this.getDDGroup();
120624         this.callParent([headerCt.el]);
120625         this.proxy.el.addCls(Ext.baseCSSPrefix + 'grid-col-dd');
120626     },
120627
120628     getDDGroup: function() {
120629         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
120630     },
120631
120632     getDragData: function(e) {
120633         var header = e.getTarget('.'+this.colHeaderCls),
120634             headerCmp;
120635
120636         if (header) {
120637             headerCmp = Ext.getCmp(header.id);
120638             if (!this.headerCt.dragging && headerCmp.draggable && !(headerCmp.isOnLeftEdge(e) || headerCmp.isOnRightEdge(e))) {
120639                 var ddel = document.createElement('div');
120640                 ddel.innerHTML = Ext.getCmp(header.id).text;
120641                 return {
120642                     ddel: ddel,
120643                     header: headerCmp
120644                 };
120645             }
120646         }
120647         return false;
120648     },
120649
120650     onBeforeDrag: function() {
120651         return !(this.headerCt.dragging || this.disabled);
120652     },
120653
120654     onInitDrag: function() {
120655         this.headerCt.dragging = true;
120656         this.callParent(arguments);
120657     },
120658
120659     onDragDrop: function() {
120660         this.headerCt.dragging = false;
120661         this.callParent(arguments);
120662     },
120663
120664     afterRepair: function() {
120665         this.callParent();
120666         this.headerCt.dragging = false;
120667     },
120668
120669     getRepairXY: function() {
120670         return this.dragData.header.el.getXY();
120671     },
120672     
120673     disable: function() {
120674         this.disabled = true;
120675     },
120676     
120677     enable: function() {
120678         this.disabled = false;
120679     }
120680 });
120681
120682 /**
120683  * @class Ext.grid.header.DropZone
120684  * @extends Ext.dd.DropZone
120685  * @private
120686  */
120687 Ext.define('Ext.grid.header.DropZone', {
120688     extend: 'Ext.dd.DropZone',
120689     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
120690     proxyOffsets: [-4, -9],
120691
120692     constructor: function(headerCt){
120693         this.headerCt = headerCt;
120694         this.ddGroup = this.getDDGroup();
120695         this.callParent([headerCt.el]);
120696     },
120697
120698     getDDGroup: function() {
120699         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
120700     },
120701
120702     getTargetFromEvent : function(e){
120703         return e.getTarget('.' + this.colHeaderCls);
120704     },
120705
120706     getTopIndicator: function() {
120707         if (!this.topIndicator) {
120708             this.topIndicator = Ext.DomHelper.append(Ext.getBody(), {
120709                 cls: "col-move-top",
120710                 html: "&#160;"
120711             }, true);
120712         }
120713         return this.topIndicator;
120714     },
120715
120716     getBottomIndicator: function() {
120717         if (!this.bottomIndicator) {
120718             this.bottomIndicator = Ext.DomHelper.append(Ext.getBody(), {
120719                 cls: "col-move-bottom",
120720                 html: "&#160;"
120721             }, true);
120722         }
120723         return this.bottomIndicator;
120724     },
120725
120726     getLocation: function(e, t) {
120727         var x      = e.getXY()[0],
120728             region = Ext.fly(t).getRegion(),
120729             pos, header;
120730
120731         if ((region.right - x) <= (region.right - region.left) / 2) {
120732             pos = "after";
120733         } else {
120734             pos = "before";
120735         }
120736         return {
120737             pos: pos,
120738             header: Ext.getCmp(t.id),
120739             node: t
120740         };
120741     },
120742
120743     positionIndicator: function(draggedHeader, node, e){
120744         var location = this.getLocation(e, node),
120745             header = location.header,
120746             pos    = location.pos,
120747             nextHd = draggedHeader.nextSibling('gridcolumn:not([hidden])'),
120748             prevHd = draggedHeader.previousSibling('gridcolumn:not([hidden])'),
120749             region, topIndicator, bottomIndicator, topAnchor, bottomAnchor,
120750             topXY, bottomXY, headerCtEl, minX, maxX;
120751
120752         // Cannot drag beyond non-draggable start column
120753         if (!header.draggable && header.getIndex() == 0) {
120754             return false;
120755         }
120756
120757         this.lastLocation = location;
120758
120759         if ((draggedHeader !== header) &&
120760             ((pos === "before" && nextHd !== header) ||
120761             (pos === "after" && prevHd !== header)) &&
120762             !header.isDescendantOf(draggedHeader)) {
120763
120764             // As we move in between different DropZones that are in the same
120765             // group (such as the case when in a locked grid), invalidateDrop
120766             // on the other dropZones.
120767             var allDropZones = Ext.dd.DragDropManager.getRelated(this),
120768                 ln = allDropZones.length,
120769                 i  = 0,
120770                 dropZone;
120771
120772             for (; i < ln; i++) {
120773                 dropZone = allDropZones[i];
120774                 if (dropZone !== this && dropZone.invalidateDrop) {
120775                     dropZone.invalidateDrop();
120776                 }
120777             }
120778
120779
120780             this.valid = true;
120781             topIndicator = this.getTopIndicator();
120782             bottomIndicator = this.getBottomIndicator();
120783             if (pos === 'before') {
120784                 topAnchor = 'tl';
120785                 bottomAnchor = 'bl';
120786             } else {
120787                 topAnchor = 'tr';
120788                 bottomAnchor = 'br';
120789             }
120790             topXY = header.el.getAnchorXY(topAnchor);
120791             bottomXY = header.el.getAnchorXY(bottomAnchor);
120792
120793             // constrain the indicators to the viewable section
120794             headerCtEl = this.headerCt.el;
120795             minX = headerCtEl.getLeft();
120796             maxX = headerCtEl.getRight();
120797
120798             topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
120799             bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);
120800
120801             // adjust by offsets, this is to center the arrows so that they point
120802             // at the split point
120803             topXY[0] -= 4;
120804             topXY[1] -= 9;
120805             bottomXY[0] -= 4;
120806
120807             // position and show indicators
120808             topIndicator.setXY(topXY);
120809             bottomIndicator.setXY(bottomXY);
120810             topIndicator.show();
120811             bottomIndicator.show();
120812         // invalidate drop operation and hide indicators
120813         } else {
120814             this.invalidateDrop();
120815         }
120816     },
120817
120818     invalidateDrop: function() {
120819         this.valid = false;
120820         this.hideIndicators();
120821     },
120822
120823     onNodeOver: function(node, dragZone, e, data) {
120824         if (data.header.el.dom !== node) {
120825             this.positionIndicator(data.header, node, e);
120826         }
120827         return this.valid ? this.dropAllowed : this.dropNotAllowed;
120828     },
120829
120830     hideIndicators: function() {
120831         this.getTopIndicator().hide();
120832         this.getBottomIndicator().hide();
120833     },
120834
120835     onNodeOut: function() {
120836         this.hideIndicators();
120837     },
120838
120839     onNodeDrop: function(node, dragZone, e, data) {
120840         if (this.valid) {
120841             this.invalidateDrop();
120842             var hd = data.header,
120843                 lastLocation = this.lastLocation,
120844                 fromCt  = hd.ownerCt,
120845                 fromIdx = fromCt.items.indexOf(hd), // Container.items is a MixedCollection
120846                 toCt    = lastLocation.header.ownerCt,
120847                 toIdx   = toCt.items.indexOf(lastLocation.header),
120848                 headerCt = this.headerCt,
120849                 groupCt,
120850                 scrollerOwner;
120851
120852             if (lastLocation.pos === 'after') {
120853                 toIdx++;
120854             }
120855
120856             // If we are dragging in between two HeaderContainers that have had the lockable
120857             // mixin injected we will lock/unlock headers in between sections. Note that lockable
120858             // does NOT currently support grouped headers.
120859             if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && toCt.lockedCt) {
120860                 scrollerOwner = fromCt.up('[scrollerOwner]');
120861                 scrollerOwner.lock(hd, toIdx);
120862             } else if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && fromCt.lockedCt) {
120863                 scrollerOwner = fromCt.up('[scrollerOwner]');
120864                 scrollerOwner.unlock(hd, toIdx);
120865             } else {
120866                 // If dragging rightwards, then after removal, the insertion index will be one less when moving
120867                 // in between the same container.
120868                 if ((fromCt === toCt) && (toIdx > fromCt.items.indexOf(hd))) {
120869                     toIdx--;
120870                 }
120871
120872                 // Remove dragged header from where it was without destroying it or relaying its Container
120873                 if (fromCt !== toCt) {
120874                     fromCt.suspendLayout = true;
120875                     fromCt.remove(hd, false);
120876                     fromCt.suspendLayout = false;
120877                 }
120878
120879                 // Dragged the last header out of the fromCt group... The fromCt group must die
120880                 if (fromCt.isGroupHeader) {
120881                     if (!fromCt.items.getCount()) {
120882                         groupCt = fromCt.ownerCt;
120883                         groupCt.suspendLayout = true;
120884                         groupCt.remove(fromCt, false);
120885                         fromCt.el.dom.parentNode.removeChild(fromCt.el.dom);
120886                         groupCt.suspendLayout = false;
120887                     } else {
120888                         fromCt.minWidth = fromCt.getWidth() - hd.getWidth();
120889                         fromCt.setWidth(fromCt.minWidth);
120890                     }
120891                 }
120892
120893                 // Move dragged header into its drop position
120894                 toCt.suspendLayout = true;
120895                 if (fromCt === toCt) {
120896                     toCt.move(fromIdx, toIdx);
120897                 } else {
120898                     toCt.insert(toIdx, hd);
120899                 }
120900                 toCt.suspendLayout = false;
120901
120902                 // Group headers acquire the aggregate width of their child headers
120903                 // Therefore a child header may not flex; it must contribute a fixed width.
120904                 // But we restore the flex value when moving back into the main header container
120905                 if (toCt.isGroupHeader) {
120906                     hd.savedFlex = hd.flex;
120907                     delete hd.flex;
120908                     hd.width = hd.getWidth();
120909                     // When there was previously a flex, we need to ensure we don't count for the
120910                     // border twice.
120911                     toCt.minWidth = toCt.getWidth() + hd.getWidth() - (hd.savedFlex ? 1 : 0);
120912                     toCt.setWidth(toCt.minWidth);
120913                 } else {
120914                     if (hd.savedFlex) {
120915                         hd.flex = hd.savedFlex;
120916                         delete hd.width;
120917                     }
120918                 }
120919
120920
120921                 // Refresh columns cache in case we remove an emptied group column
120922                 headerCt.purgeCache();
120923                 headerCt.doLayout();
120924                 headerCt.onHeaderMoved(hd, fromIdx, toIdx);
120925                 // Emptied group header can only be destroyed after the header and grid have been refreshed
120926                 if (!fromCt.items.getCount()) {
120927                     fromCt.destroy();
120928                 }
120929             }
120930         }
120931     }
120932 });
120933
120934 /**
120935  * This class provides an abstract grid editing plugin on selected {@link Ext.grid.column.Column columns}.
120936  * The editable columns are specified by providing an {@link Ext.grid.column.Column#editor editor}
120937  * in the {@link Ext.grid.column.Column column configuration}.
120938  *
120939  * **Note:** This class should not be used directly. See {@link Ext.grid.plugin.CellEditing} and
120940  * {@link Ext.grid.plugin.RowEditing}.
120941  */
120942 Ext.define('Ext.grid.plugin.Editing', {
120943     alias: 'editing.editing',
120944
120945     requires: [
120946         'Ext.grid.column.Column',
120947         'Ext.util.KeyNav'
120948     ],
120949
120950     mixins: {
120951         observable: 'Ext.util.Observable'
120952     },
120953
120954     /**
120955      * @cfg {Number} clicksToEdit
120956      * The number of clicks on a grid required to display the editor.
120957      */
120958     clicksToEdit: 2,
120959
120960     // private
120961     defaultFieldXType: 'textfield',
120962
120963     // cell, row, form
120964     editStyle: '',
120965
120966     constructor: function(config) {
120967         var me = this;
120968         Ext.apply(me, config);
120969
120970         me.addEvents(
120971             // Doc'ed in separate editing plugins
120972             'beforeedit',
120973
120974             // Doc'ed in separate editing plugins
120975             'edit',
120976
120977             // Doc'ed in separate editing plugins
120978             'validateedit'
120979         );
120980         me.mixins.observable.constructor.call(me);
120981         // TODO: Deprecated, remove in 5.0
120982         me.relayEvents(me, ['afteredit'], 'after');
120983     },
120984
120985     // private
120986     init: function(grid) {
120987         var me = this;
120988
120989         me.grid = grid;
120990         me.view = grid.view;
120991         me.initEvents();
120992         me.mon(grid, 'reconfigure', me.onReconfigure, me);
120993         me.onReconfigure();
120994
120995         grid.relayEvents(me, ['beforeedit', 'edit', 'validateedit']);
120996         // Marks the grid as editable, so that the SelectionModel
120997         // can make appropriate decisions during navigation
120998         grid.isEditable = true;
120999         grid.editingPlugin = grid.view.editingPlugin = me;
121000     },
121001
121002     /**
121003      * Fires after the grid is reconfigured
121004      * @private
121005      */
121006     onReconfigure: function(){
121007         this.initFieldAccessors(this.view.getGridColumns());
121008     },
121009
121010     /**
121011      * @private
121012      * AbstractComponent calls destroy on all its plugins at destroy time.
121013      */
121014     destroy: function() {
121015         var me = this,
121016             grid = me.grid,
121017             headerCt = grid.headerCt,
121018             events = grid.events;
121019
121020         Ext.destroy(me.keyNav);
121021         me.removeFieldAccessors(grid.getView().getGridColumns());
121022
121023         // Clear all listeners from all our events, clear all managed listeners we added to other Observables
121024         me.clearListeners();
121025
121026         delete me.grid.editingPlugin;
121027         delete me.grid.view.editingPlugin;
121028         delete me.grid;
121029         delete me.view;
121030         delete me.editor;
121031         delete me.keyNav;
121032     },
121033
121034     // private
121035     getEditStyle: function() {
121036         return this.editStyle;
121037     },
121038
121039     // private
121040     initFieldAccessors: function(column) {
121041         var me = this;
121042
121043         if (Ext.isArray(column)) {
121044             Ext.Array.forEach(column, me.initFieldAccessors, me);
121045             return;
121046         }
121047
121048         // Augment the Header class to have a getEditor and setEditor method
121049         // Important: Only if the header does not have its own implementation.
121050         Ext.applyIf(column, {
121051             getEditor: function(record, defaultField) {
121052                 return me.getColumnField(this, defaultField);
121053             },
121054
121055             setEditor: function(field) {
121056                 me.setColumnField(this, field);
121057             }
121058         });
121059     },
121060
121061     // private
121062     removeFieldAccessors: function(column) {
121063         var me = this;
121064
121065         if (Ext.isArray(column)) {
121066             Ext.Array.forEach(column, me.removeFieldAccessors, me);
121067             return;
121068         }
121069
121070         delete column.getEditor;
121071         delete column.setEditor;
121072     },
121073
121074     // private
121075     // remaps to the public API of Ext.grid.column.Column.getEditor
121076     getColumnField: function(columnHeader, defaultField) {
121077         var field = columnHeader.field;
121078
121079         if (!field && columnHeader.editor) {
121080             field = columnHeader.editor;
121081             delete columnHeader.editor;
121082         }
121083
121084         if (!field && defaultField) {
121085             field = defaultField;
121086         }
121087
121088         if (field) {
121089             if (Ext.isString(field)) {
121090                 field = { xtype: field };
121091             }
121092             if (Ext.isObject(field) && !field.isFormField) {
121093                 field = Ext.ComponentManager.create(field, this.defaultFieldXType);
121094                 columnHeader.field = field;
121095             }
121096
121097             Ext.apply(field, {
121098                 name: columnHeader.dataIndex
121099             });
121100
121101             return field;
121102         }
121103     },
121104
121105     // private
121106     // remaps to the public API of Ext.grid.column.Column.setEditor
121107     setColumnField: function(column, field) {
121108         if (Ext.isObject(field) && !field.isFormField) {
121109             field = Ext.ComponentManager.create(field, this.defaultFieldXType);
121110         }
121111         column.field = field;
121112     },
121113
121114     // private
121115     initEvents: function() {
121116         var me = this;
121117         me.initEditTriggers();
121118         me.initCancelTriggers();
121119     },
121120
121121     // @abstract
121122     initCancelTriggers: Ext.emptyFn,
121123
121124     // private
121125     initEditTriggers: function() {
121126         var me = this,
121127             view = me.view,
121128             clickEvent = me.clicksToEdit === 1 ? 'click' : 'dblclick';
121129
121130         // Start editing
121131         me.mon(view, 'cell' + clickEvent, me.startEditByClick, me);
121132         view.on('render', function() {
121133             me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
121134                 enter: me.onEnterKey,
121135                 esc: me.onEscKey,
121136                 scope: me
121137             });
121138         }, me, { single: true });
121139     },
121140
121141     // private
121142     onEnterKey: function(e) {
121143         var me = this,
121144             grid = me.grid,
121145             selModel = grid.getSelectionModel(),
121146             record,
121147             columnHeader = grid.headerCt.getHeaderAtIndex(0);
121148
121149         // Calculate editing start position from SelectionModel
121150         // CellSelectionModel
121151         if (selModel.getCurrentPosition) {
121152             pos = selModel.getCurrentPosition();
121153             record = grid.store.getAt(pos.row);
121154             columnHeader = grid.headerCt.getHeaderAtIndex(pos.column);
121155         }
121156         // RowSelectionModel
121157         else {
121158             record = selModel.getLastSelected();
121159         }
121160         me.startEdit(record, columnHeader);
121161     },
121162
121163     // private
121164     onEscKey: function(e) {
121165         this.cancelEdit();
121166     },
121167
121168     // private
121169     startEditByClick: function(view, cell, colIdx, record, row, rowIdx, e) {
121170         this.startEdit(record, view.getHeaderAtIndex(colIdx));
121171     },
121172
121173     /**
121174      * @private
121175      * @template
121176      * Template method called before editing begins.
121177      * @param {Object} context The current editing context
121178      * @return {Boolean} Return false to cancel the editing process
121179      */
121180     beforeEdit: Ext.emptyFn,
121181
121182     /**
121183      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
121184      * @param {Ext.data.Model/Number} record The Store data record which backs the row to be edited, or index of the record in Store.
121185      * @param {Ext.grid.column.Column/Number} columnHeader The Column object defining the column to be edited, or index of the column.
121186      */
121187     startEdit: function(record, columnHeader) {
121188         var me = this,
121189             context = me.getEditingContext(record, columnHeader);
121190
121191         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
121192             return false;
121193         }
121194
121195         me.context = context;
121196         me.editing = true;
121197     },
121198
121199     /**
121200      * @private
121201      * Collects all information necessary for any subclasses to perform their editing functions.
121202      * @param record
121203      * @param columnHeader
121204      * @returns {Object} The editing context based upon the passed record and column
121205      */
121206     getEditingContext: function(record, columnHeader) {
121207         var me = this,
121208             grid = me.grid,
121209             store = grid.store,
121210             rowIdx,
121211             colIdx,
121212             view = grid.getView(),
121213             value;
121214
121215         // If they'd passed numeric row, column indices, look them up.
121216         if (Ext.isNumber(record)) {
121217             rowIdx = record;
121218             record = store.getAt(rowIdx);
121219         } else {
121220             rowIdx = store.indexOf(record);
121221         }
121222         if (Ext.isNumber(columnHeader)) {
121223             colIdx = columnHeader;
121224             columnHeader = grid.headerCt.getHeaderAtIndex(colIdx);
121225         } else {
121226             colIdx = columnHeader.getIndex();
121227         }
121228
121229         value = record.get(columnHeader.dataIndex);
121230         return {
121231             grid: grid,
121232             record: record,
121233             field: columnHeader.dataIndex,
121234             value: value,
121235             row: view.getNode(rowIdx),
121236             column: columnHeader,
121237             rowIdx: rowIdx,
121238             colIdx: colIdx
121239         };
121240     },
121241
121242     /**
121243      * Cancels any active edit that is in progress.
121244      */
121245     cancelEdit: function() {
121246         this.editing = false;
121247     },
121248
121249     /**
121250      * Completes the edit if there is an active edit in progress.
121251      */
121252     completeEdit: function() {
121253         var me = this;
121254
121255         if (me.editing && me.validateEdit()) {
121256             me.fireEvent('edit', me.context);
121257         }
121258
121259         delete me.context;
121260         me.editing = false;
121261     },
121262
121263     // @abstract
121264     validateEdit: function() {
121265         var me = this,
121266             context = me.context;
121267
121268         return me.fireEvent('validateedit', me, context) !== false && !context.cancel;
121269     }
121270 });
121271
121272 /**
121273  * The Ext.grid.plugin.CellEditing plugin injects editing at a cell level for a Grid. Only a single
121274  * cell will be editable at a time. The field that will be used for the editor is defined at the
121275  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
121276  *
121277  * If an editor is not specified for a particular column then that cell will not be editable and it will
121278  * be skipped when activated via the mouse or the keyboard.
121279  *
121280  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
121281  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
121282  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
121283  *
121284  *     @example
121285  *     Ext.create('Ext.data.Store', {
121286  *         storeId:'simpsonsStore',
121287  *         fields:['name', 'email', 'phone'],
121288  *         data:{'items':[
121289  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
121290  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
121291  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
121292  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
121293  *         ]},
121294  *         proxy: {
121295  *             type: 'memory',
121296  *             reader: {
121297  *                 type: 'json',
121298  *                 root: 'items'
121299  *             }
121300  *         }
121301  *     });
121302  *
121303  *     Ext.create('Ext.grid.Panel', {
121304  *         title: 'Simpsons',
121305  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
121306  *         columns: [
121307  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
121308  *             {header: 'Email', dataIndex: 'email', flex:1,
121309  *                 editor: {
121310  *                     xtype: 'textfield',
121311  *                     allowBlank: false
121312  *                 }
121313  *             },
121314  *             {header: 'Phone', dataIndex: 'phone'}
121315  *         ],
121316  *         selType: 'cellmodel',
121317  *         plugins: [
121318  *             Ext.create('Ext.grid.plugin.CellEditing', {
121319  *                 clicksToEdit: 1
121320  *             })
121321  *         ],
121322  *         height: 200,
121323  *         width: 400,
121324  *         renderTo: Ext.getBody()
121325  *     });
121326  */
121327 Ext.define('Ext.grid.plugin.CellEditing', {
121328     alias: 'plugin.cellediting',
121329     extend: 'Ext.grid.plugin.Editing',
121330     requires: ['Ext.grid.CellEditor', 'Ext.util.DelayedTask'],
121331
121332     constructor: function() {
121333         /**
121334          * @event beforeedit
121335          * Fires before cell editing is triggered. Return false from event handler to stop the editing.
121336          *
121337          * @param {Object} e An edit event with the following properties:
121338          *
121339          * - grid - The grid
121340          * - record - The record being edited
121341          * - field - The field name being edited
121342          * - value - The value for the field being edited.
121343          * - row - The grid table row
121344          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
121345          * - rowIdx - The row index that is being edited
121346          * - colIdx - The column index that is being edited
121347          * - cancel - Set this to true to cancel the edit or return false from your handler.
121348          */
121349         /**
121350          * @event edit
121351          * Fires after a cell is edited. Usage example:
121352          *
121353          *     grid.on('edit', function(editor, e) {
121354          *         // commit the changes right after editing finished
121355          *         e.record.commit();
121356          *     };
121357          *
121358          * @param {Ext.grid.plugin.Editing} editor
121359          * @param {Object} e An edit event with the following properties:
121360          *
121361          * - grid - The grid
121362          * - record - The record that was edited
121363          * - field - The field name that was edited
121364          * - value - The value being set
121365          * - originalValue - The original value for the field, before the edit.
121366          * - row - The grid table row
121367          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.
121368          * - rowIdx - The row index that was edited
121369          * - colIdx - The column index that was edited
121370          */
121371         /**
121372          * @event validateedit
121373          * Fires after a cell is edited, but before the value is set in the record. Return false from event handler to
121374          * cancel the change.
121375          *
121376          * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
121377          * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for
121378          * example) and then setting the field's new value in the Record directly:
121379          *
121380          *     grid.on('validateedit', function(editor, e) {
121381          *       var myTargetRow = 6;
121382          *
121383          *       if (e.row == myTargetRow) {
121384          *         e.cancel = true;
121385          *         e.record.data[e.field] = e.value;
121386          *       }
121387          *     });
121388          *
121389          * @param {Ext.grid.plugin.Editing} editor
121390          * @param {Object} e An edit event with the following properties:
121391          *
121392          * - grid - The grid
121393          * - record - The record being edited
121394          * - field - The field name being edited
121395          * - value - The value being set
121396          * - originalValue - The original value for the field, before the edit.
121397          * - row - The grid table row
121398          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
121399          * - rowIdx - The row index that is being edited
121400          * - colIdx - The column index that is being edited
121401          * - cancel - Set this to true to cancel the edit or return false from your handler.
121402          */
121403         this.callParent(arguments);
121404         this.editors = Ext.create('Ext.util.MixedCollection', false, function(editor) {
121405             return editor.editorId;
121406         });
121407         this.editTask = Ext.create('Ext.util.DelayedTask');
121408     },
121409     
121410     onReconfigure: function(){
121411         this.editors.clear();
121412         this.callParent();    
121413     },
121414
121415     /**
121416      * @private
121417      * AbstractComponent calls destroy on all its plugins at destroy time.
121418      */
121419     destroy: function() {
121420         var me = this;
121421         me.editTask.cancel();
121422         me.editors.each(Ext.destroy, Ext);
121423         me.editors.clear();
121424         me.callParent(arguments);
121425     },
121426     
121427     onBodyScroll: function() {
121428         var ed = this.getActiveEditor();
121429         if (ed && ed.field) {
121430             if (ed.field.triggerBlur) {
121431                 ed.field.triggerBlur();
121432             } else {
121433                 ed.field.blur();
121434             }
121435         }
121436     },
121437
121438     // private
121439     // Template method called from base class's initEvents
121440     initCancelTriggers: function() {
121441         var me   = this,
121442             grid = me.grid,
121443             view = grid.view;
121444             
121445         view.addElListener('mousewheel', me.cancelEdit, me);
121446         me.mon(view, 'bodyscroll', me.onBodyScroll, me);
121447         me.mon(grid, {
121448             columnresize: me.cancelEdit,
121449             columnmove: me.cancelEdit,
121450             scope: me
121451         });
121452     },
121453
121454     /**
121455      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
121456      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
121457      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
121458      */
121459     startEdit: function(record, columnHeader) {
121460         var me = this,
121461             value = record.get(columnHeader.dataIndex),
121462             context = me.getEditingContext(record, columnHeader),
121463             ed;
121464
121465         record = context.record;
121466         columnHeader = context.column;
121467
121468         // Complete the edit now, before getting the editor's target
121469         // cell DOM element. Completing the edit causes a view refresh.
121470         me.completeEdit();
121471
121472         context.originalValue = context.value = value;
121473         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
121474             return false;
121475         }
121476         
121477         // See if the field is editable for the requested record
121478         if (columnHeader && !columnHeader.getEditor(record)) {
121479             return false;
121480         }
121481         
121482         ed = me.getEditor(record, columnHeader);
121483         if (ed) {
121484             me.context = context;
121485             me.setActiveEditor(ed);
121486             me.setActiveRecord(record);
121487             me.setActiveColumn(columnHeader);
121488
121489             // Defer, so we have some time between view scroll to sync up the editor
121490             me.editTask.delay(15, ed.startEdit, ed, [me.getCell(record, columnHeader), value]);
121491         } else {
121492             // BrowserBug: WebKit & IE refuse to focus the element, rather
121493             // it will focus it and then immediately focus the body. This
121494             // temporary hack works for Webkit and IE6. IE7 and 8 are still
121495             // broken
121496             me.grid.getView().getEl(columnHeader).focus((Ext.isWebKit || Ext.isIE) ? 10 : false);
121497         }
121498     },
121499
121500     completeEdit: function() {
121501         var activeEd = this.getActiveEditor();
121502         if (activeEd) {
121503             activeEd.completeEdit();
121504         }
121505     },
121506
121507     // internal getters/setters
121508     setActiveEditor: function(ed) {
121509         this.activeEditor = ed;
121510     },
121511
121512     getActiveEditor: function() {
121513         return this.activeEditor;
121514     },
121515
121516     setActiveColumn: function(column) {
121517         this.activeColumn = column;
121518     },
121519
121520     getActiveColumn: function() {
121521         return this.activeColumn;
121522     },
121523
121524     setActiveRecord: function(record) {
121525         this.activeRecord = record;
121526     },
121527
121528     getActiveRecord: function() {
121529         return this.activeRecord;
121530     },
121531
121532     getEditor: function(record, column) {
121533         var me = this,
121534             editors = me.editors,
121535             editorId = column.getItemId(),
121536             editor = editors.getByKey(editorId);
121537
121538         if (editor) {
121539             return editor;
121540         } else {
121541             editor = column.getEditor(record);
121542             if (!editor) {
121543                 return false;
121544             }
121545
121546             // Allow them to specify a CellEditor in the Column
121547             if (!(editor instanceof Ext.grid.CellEditor)) {
121548                 editor = Ext.create('Ext.grid.CellEditor', {
121549                     editorId: editorId,
121550                     field: editor
121551                 });
121552             }
121553             editor.parentEl = me.grid.getEditorParent();
121554             // editor.parentEl should be set here.
121555             editor.on({
121556                 scope: me,
121557                 specialkey: me.onSpecialKey,
121558                 complete: me.onEditComplete,
121559                 canceledit: me.cancelEdit
121560             });
121561             editors.add(editor);
121562             return editor;
121563         }
121564     },
121565     
121566     // inherit docs
121567     setColumnField: function(column, field) {
121568         var ed = this.editors.getByKey(column.getItemId());
121569         Ext.destroy(ed, column.field);
121570         this.editors.removeAtKey(column.getItemId());
121571         this.callParent(arguments);
121572     },
121573
121574     /**
121575      * Gets the cell (td) for a particular record and column.
121576      * @param {Ext.data.Model} record
121577      * @param {Ext.grid.column.Column} column
121578      * @private
121579      */
121580     getCell: function(record, column) {
121581         return this.grid.getView().getCell(record, column);
121582     },
121583
121584     onSpecialKey: function(ed, field, e) {
121585         var grid = this.grid,
121586             sm;
121587         if (e.getKey() === e.TAB) {
121588             e.stopEvent();
121589             sm = grid.getSelectionModel();
121590             if (sm.onEditorTab) {
121591                 sm.onEditorTab(this, e);
121592             }
121593         }
121594     },
121595
121596     onEditComplete : function(ed, value, startValue) {
121597         var me = this,
121598             grid = me.grid,
121599             sm = grid.getSelectionModel(),
121600             activeColumn = me.getActiveColumn(),
121601             dataIndex;
121602
121603         if (activeColumn) {
121604             dataIndex = activeColumn.dataIndex;
121605
121606             me.setActiveEditor(null);
121607             me.setActiveColumn(null);
121608             me.setActiveRecord(null);
121609             delete sm.wasEditing;
121610     
121611             if (!me.validateEdit()) {
121612                 return;
121613             }
121614             // Only update the record if the new value is different than the
121615             // startValue, when the view refreshes its el will gain focus
121616             if (value !== startValue) {
121617                 me.context.record.set(dataIndex, value);
121618             // Restore focus back to the view's element.
121619             } else {
121620                 grid.getView().getEl(activeColumn).focus();
121621             }
121622             me.context.value = value;
121623             me.fireEvent('edit', me, me.context);
121624         }
121625     },
121626
121627     /**
121628      * Cancels any active editing.
121629      */
121630     cancelEdit: function() {
121631         var me = this,
121632             activeEd = me.getActiveEditor(),
121633             viewEl = me.grid.getView().getEl(me.getActiveColumn());
121634
121635         me.setActiveEditor(null);
121636         me.setActiveColumn(null);
121637         me.setActiveRecord(null);
121638         if (activeEd) {
121639             activeEd.cancelEdit();
121640             viewEl.focus();
121641         }
121642     },
121643
121644     /**
121645      * Starts editing by position (row/column)
121646      * @param {Object} position A position with keys of row and column.
121647      */
121648     startEditByPosition: function(position) {
121649         var me = this,
121650             grid = me.grid,
121651             sm = grid.getSelectionModel(),
121652             editRecord = grid.store.getAt(position.row),
121653             editColumnHeader = grid.headerCt.getHeaderAtIndex(position.column);
121654
121655         if (sm.selectByPosition) {
121656             sm.selectByPosition(position);
121657         }
121658         me.startEdit(editRecord, editColumnHeader);
121659     }
121660 });
121661 /**
121662  * This plugin provides drag and/or drop functionality for a GridView.
121663  *
121664  * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a {@link
121665  * Ext.grid.View GridView} and loads the data object which is passed to a cooperating {@link Ext.dd.DragZone DragZone}'s
121666  * methods with the following properties:
121667  *
121668  * - `copy` : Boolean
121669  *
121670  *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` _and_
121671  *   the control key was pressed when the drag operation was begun.
121672  *
121673  * - `view` : GridView
121674  *
121675  *   The source GridView from which the drag originated.
121676  *
121677  * - `ddel` : HtmlElement
121678  *
121679  *   The drag proxy element which moves with the mouse
121680  *
121681  * - `item` : HtmlElement
121682  *
121683  *   The GridView node upon which the mousedown event was registered.
121684  *
121685  * - `records` : Array
121686  *
121687  *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
121688  *
121689  * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
121690  * members of the same ddGroup which processes such data objects.
121691  *
121692  * Adding this plugin to a view means that two new events may be fired from the client GridView, `{@link #beforedrop
121693  * beforedrop}` and `{@link #drop drop}`
121694  *
121695  *     @example
121696  *     Ext.create('Ext.data.Store', {
121697  *         storeId:'simpsonsStore',
121698  *         fields:['name'],
121699  *         data: [["Lisa"], ["Bart"], ["Homer"], ["Marge"]],
121700  *         proxy: {
121701  *             type: 'memory',
121702  *             reader: 'array'
121703  *         }
121704  *     });
121705  *
121706  *     Ext.create('Ext.grid.Panel', {
121707  *         store: 'simpsonsStore',
121708  *         columns: [
121709  *             {header: 'Name',  dataIndex: 'name', flex: true}
121710  *         ],
121711  *         viewConfig: {
121712  *             plugins: {
121713  *                 ptype: 'gridviewdragdrop',
121714  *                 dragText: 'Drag and drop to reorganize'
121715  *             }
121716  *         },
121717  *         height: 200,
121718  *         width: 400,
121719  *         renderTo: Ext.getBody()
121720  *     });
121721  */
121722 Ext.define('Ext.grid.plugin.DragDrop', {
121723     extend: 'Ext.AbstractPlugin',
121724     alias: 'plugin.gridviewdragdrop',
121725
121726     uses: [
121727         'Ext.view.DragZone',
121728         'Ext.grid.ViewDropZone'
121729     ],
121730
121731     /**
121732      * @event beforedrop
121733      * **This event is fired through the GridView. Add listeners to the GridView object**
121734      *
121735      * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the GridView.
121736      *
121737      * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
121738      *
121739      * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
121740      * back to the point from which the drag began.
121741      *
121742      * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
121743      * was valid, and that the repair operation should not take place.
121744      *
121745      * Any other return value continues with the data transfer operation.
121746      *
121747      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
121748      * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
121749      *
121750      * - copy : Boolean
121751      *
121752      *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
121753      *   the control key was pressed when the drag operation was begun
121754      *
121755      * - view : GridView
121756      *
121757      *   The source GridView from which the drag originated.
121758      *
121759      * - ddel : HtmlElement
121760      *
121761      *   The drag proxy element which moves with the mouse
121762      *
121763      * - item : HtmlElement
121764      *
121765      *   The GridView node upon which the mousedown event was registered.
121766      *
121767      * - records : Array
121768      *
121769      *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
121770      *
121771      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
121772      *
121773      * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
121774      * of the node.
121775      *
121776      * @param {Function} dropFunction
121777      *
121778      * A function to call to complete the data transfer operation and either move or copy Model instances from the
121779      * source View's Store to the destination View's Store.
121780      *
121781      * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
121782      * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
121783      *
121784      * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
121785      */
121786
121787     /**
121788      * @event drop
121789      * **This event is fired through the GridView. Add listeners to the GridView object** Fired when a drop operation
121790      * has been completed and the data has been moved or copied.
121791      *
121792      * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
121793      *
121794      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
121795      * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
121796      *
121797      * - copy : Boolean
121798      *
121799      *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
121800      *   the control key was pressed when the drag operation was begun
121801      *
121802      * - view : GridView
121803      *
121804      *   The source GridView from which the drag originated.
121805      *
121806      * - ddel : HtmlElement
121807      *
121808      *   The drag proxy element which moves with the mouse
121809      *
121810      * - item : HtmlElement
121811      *
121812      *   The GridView node upon which the mousedown event was registered.
121813      *
121814      * - records : Array
121815      *
121816      *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
121817      *
121818      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
121819      *
121820      * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
121821      * of the node.
121822      */
121823
121824     dragText : '{0} selected row{1}',
121825
121826     /**
121827      * @cfg {String} ddGroup
121828      * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
121829      * DropZone used by this plugin will only interact with other drag drop objects in the same group.
121830      */
121831     ddGroup : "GridDD",
121832
121833     /**
121834      * @cfg {String} dragGroup
121835      * The ddGroup to which the DragZone will belong.
121836      *
121837      * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
121838      * Drag/DropZones which are members of the same ddGroup.
121839      */
121840
121841     /**
121842      * @cfg {String} dropGroup
121843      * The ddGroup to which the DropZone will belong.
121844      *
121845      * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
121846      * Drag/DropZones which are members of the same ddGroup.
121847      */
121848
121849     /**
121850      * @cfg {Boolean} enableDrop
121851      * False to disallow the View from accepting drop gestures.
121852      */
121853     enableDrop: true,
121854
121855     /**
121856      * @cfg {Boolean} enableDrag
121857      * False to disallow dragging items from the View.
121858      */
121859     enableDrag: true,
121860
121861     init : function(view) {
121862         view.on('render', this.onViewRender, this, {single: true});
121863     },
121864
121865     /**
121866      * @private
121867      * AbstractComponent calls destroy on all its plugins at destroy time.
121868      */
121869     destroy: function() {
121870         Ext.destroy(this.dragZone, this.dropZone);
121871     },
121872
121873     enable: function() {
121874         var me = this;
121875         if (me.dragZone) {
121876             me.dragZone.unlock();
121877         }
121878         if (me.dropZone) {
121879             me.dropZone.unlock();
121880         }
121881         me.callParent();
121882     },
121883
121884     disable: function() {
121885         var me = this;
121886         if (me.dragZone) {
121887             me.dragZone.lock();
121888         }
121889         if (me.dropZone) {
121890             me.dropZone.lock();
121891         }
121892         me.callParent();
121893     },
121894
121895     onViewRender : function(view) {
121896         var me = this;
121897
121898         if (me.enableDrag) {
121899             me.dragZone = Ext.create('Ext.view.DragZone', {
121900                 view: view,
121901                 ddGroup: me.dragGroup || me.ddGroup,
121902                 dragText: me.dragText
121903             });
121904         }
121905
121906         if (me.enableDrop) {
121907             me.dropZone = Ext.create('Ext.grid.ViewDropZone', {
121908                 view: view,
121909                 ddGroup: me.dropGroup || me.ddGroup
121910             });
121911         }
121912     }
121913 });
121914 /**
121915  * @class Ext.grid.plugin.HeaderReorderer
121916  * @extends Ext.util.Observable
121917  * @private
121918  */
121919 Ext.define('Ext.grid.plugin.HeaderReorderer', {
121920     extend: 'Ext.util.Observable',
121921     requires: ['Ext.grid.header.DragZone', 'Ext.grid.header.DropZone'],
121922     alias: 'plugin.gridheaderreorderer',
121923
121924     init: function(headerCt) {
121925         this.headerCt = headerCt;
121926         headerCt.on('render', this.onHeaderCtRender, this);
121927     },
121928
121929     /**
121930      * @private
121931      * AbstractComponent calls destroy on all its plugins at destroy time.
121932      */
121933     destroy: function() {
121934         Ext.destroy(this.dragZone, this.dropZone);
121935     },
121936
121937     onHeaderCtRender: function() {
121938         this.dragZone = Ext.create('Ext.grid.header.DragZone', this.headerCt);
121939         this.dropZone = Ext.create('Ext.grid.header.DropZone', this.headerCt);
121940         if (this.disabled) {
121941             this.dragZone.disable();
121942         }
121943     },
121944     
121945     enable: function() {
121946         this.disabled = false;
121947         if (this.dragZone) {
121948             this.dragZone.enable();
121949         }
121950     },
121951     
121952     disable: function() {
121953         this.disabled = true;
121954         if (this.dragZone) {
121955             this.dragZone.disable();
121956         }
121957     }
121958 });
121959 /**
121960  * @class Ext.grid.plugin.HeaderResizer
121961  * @extends Ext.util.Observable
121962  *
121963  * Plugin to add header resizing functionality to a HeaderContainer.
121964  * Always resizing header to the left of the splitter you are resizing.
121965  */
121966 Ext.define('Ext.grid.plugin.HeaderResizer', {
121967     extend: 'Ext.util.Observable',
121968     requires: ['Ext.dd.DragTracker', 'Ext.util.Region'],
121969     alias: 'plugin.gridheaderresizer',
121970
121971     disabled: false,
121972
121973     /**
121974      * @cfg {Boolean} dynamic
121975      * Set to true to resize on the fly rather than using a proxy marker. Defaults to false.
121976      */
121977     configs: {
121978         dynamic: true
121979     },
121980
121981     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
121982
121983     minColWidth: 40,
121984     maxColWidth: 1000,
121985     wResizeCursor: 'col-resize',
121986     eResizeCursor: 'col-resize',
121987     // not using w and e resize bc we are only ever resizing one
121988     // column
121989     //wResizeCursor: Ext.isWebKit ? 'w-resize' : 'col-resize',
121990     //eResizeCursor: Ext.isWebKit ? 'e-resize' : 'col-resize',
121991
121992     init: function(headerCt) {
121993         this.headerCt = headerCt;
121994         headerCt.on('render', this.afterHeaderRender, this, {single: true});
121995     },
121996
121997     /**
121998      * @private
121999      * AbstractComponent calls destroy on all its plugins at destroy time.
122000      */
122001     destroy: function() {
122002         if (this.tracker) {
122003             this.tracker.destroy();
122004         }
122005     },
122006
122007     afterHeaderRender: function() {
122008         var headerCt = this.headerCt,
122009             el = headerCt.el;
122010
122011         headerCt.mon(el, 'mousemove', this.onHeaderCtMouseMove, this);
122012
122013         this.tracker = Ext.create('Ext.dd.DragTracker', {
122014             disabled: this.disabled,
122015             onBeforeStart: Ext.Function.bind(this.onBeforeStart, this),
122016             onStart: Ext.Function.bind(this.onStart, this),
122017             onDrag: Ext.Function.bind(this.onDrag, this),
122018             onEnd: Ext.Function.bind(this.onEnd, this),
122019             tolerance: 3,
122020             autoStart: 300,
122021             el: el
122022         });
122023     },
122024
122025     // As we mouse over individual headers, change the cursor to indicate
122026     // that resizing is available, and cache the resize target header for use
122027     // if/when they mousedown.
122028     onHeaderCtMouseMove: function(e, t) {
122029         if (this.headerCt.dragging) {
122030             if (this.activeHd) {
122031                 this.activeHd.el.dom.style.cursor = '';
122032                 delete this.activeHd;
122033             }
122034         } else {
122035             var headerEl = e.getTarget('.' + this.colHeaderCls, 3, true),
122036                 overHeader, resizeHeader;
122037
122038             if (headerEl){
122039                 overHeader = Ext.getCmp(headerEl.id);
122040
122041                 // On left edge, go back to the previous non-hidden header.
122042                 if (overHeader.isOnLeftEdge(e)) {
122043                     resizeHeader = overHeader.previousNode('gridcolumn:not([hidden])');
122044
122045                 }
122046                 // Else, if on the right edge, we're resizing the column we are over
122047                 else if (overHeader.isOnRightEdge(e)) {
122048                     resizeHeader = overHeader;
122049                 }
122050                 // Between the edges: we are not resizing
122051                 else {
122052                     resizeHeader = null;
122053                 }
122054
122055                 // We *are* resizing
122056                 if (resizeHeader) {
122057                     // If we're attempting to resize a group header, that cannot be resized,
122058                     // so find its last visible leaf header; Group headers are sized
122059                     // by the size of their child headers.
122060                     if (resizeHeader.isGroupHeader) {
122061                         resizeHeader = resizeHeader.down(':not([isGroupHeader]):not([hidden]):last');
122062                     }
122063
122064                     // Check if the header is resizable. Continue checking the old "fixed" property, bug also
122065                     // check whether the resizablwe property is set to false.
122066                     if (resizeHeader && !(resizeHeader.fixed || (resizeHeader.resizable === false) || this.disabled)) {
122067                         this.activeHd = resizeHeader;
122068                         overHeader.el.dom.style.cursor = this.eResizeCursor;
122069                     }
122070                 // reset
122071                 } else {
122072                     overHeader.el.dom.style.cursor = '';
122073                     delete this.activeHd;
122074                 }
122075             }
122076         }
122077     },
122078
122079     // only start when there is an activeHd
122080     onBeforeStart : function(e){
122081         var t = e.getTarget();
122082         // cache the activeHd because it will be cleared.
122083         this.dragHd = this.activeHd;
122084
122085         if (!!this.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger') && !this.headerCt.dragging) {
122086             //this.headerCt.dragging = true;
122087             this.tracker.constrainTo = this.getConstrainRegion();
122088             return true;
122089         } else {
122090             this.headerCt.dragging = false;
122091             return false;
122092         }
122093     },
122094
122095     // get the region to constrain to, takes into account max and min col widths
122096     getConstrainRegion: function() {
122097         var dragHdEl = this.dragHd.el,
122098             region   = Ext.util.Region.getRegion(dragHdEl);
122099
122100         return region.adjust(
122101             0,
122102             this.maxColWidth - dragHdEl.getWidth(),
122103             0,
122104             this.minColWidth
122105         );
122106     },
122107
122108     // initialize the left and right hand side markers around
122109     // the header that we are resizing
122110     onStart: function(e){
122111         var me       = this,
122112             dragHd   = me.dragHd,
122113             dragHdEl = dragHd.el,
122114             width    = dragHdEl.getWidth(),
122115             headerCt = me.headerCt,
122116             t        = e.getTarget();
122117
122118         if (me.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger')) {
122119             headerCt.dragging = true;
122120         }
122121
122122         me.origWidth = width;
122123
122124         // setup marker proxies
122125         if (!me.dynamic) {
122126             var xy           = dragHdEl.getXY(),
122127                 gridSection  = headerCt.up('[scrollerOwner]'),
122128                 dragHct      = me.dragHd.up(':not([isGroupHeader])'),
122129                 firstSection = dragHct.up(),
122130                 lhsMarker    = gridSection.getLhsMarker(),
122131                 rhsMarker    = gridSection.getRhsMarker(),
122132                 el           = rhsMarker.parent(),
122133                 offsetLeft   = el.getLeft(true),
122134                 offsetTop    = el.getTop(true),
122135                 topLeft      = el.translatePoints(xy),
122136                 markerHeight = firstSection.body.getHeight() + headerCt.getHeight(),
122137                 top = topLeft.top - offsetTop;
122138
122139             lhsMarker.setTop(top);
122140             rhsMarker.setTop(top);
122141             lhsMarker.setHeight(markerHeight);
122142             rhsMarker.setHeight(markerHeight);
122143             lhsMarker.setLeft(topLeft.left - offsetLeft);
122144             rhsMarker.setLeft(topLeft.left + width - offsetLeft);
122145         }
122146     },
122147
122148     // synchronize the rhsMarker with the mouse movement
122149     onDrag: function(e){
122150         if (!this.dynamic) {
122151             var xy          = this.tracker.getXY('point'),
122152                 gridSection = this.headerCt.up('[scrollerOwner]'),
122153                 rhsMarker   = gridSection.getRhsMarker(),
122154                 el          = rhsMarker.parent(),
122155                 topLeft     = el.translatePoints(xy),
122156                 offsetLeft  = el.getLeft(true);
122157
122158             rhsMarker.setLeft(topLeft.left - offsetLeft);
122159         // Resize as user interacts
122160         } else {
122161             this.doResize();
122162         }
122163     },
122164
122165     onEnd: function(e){
122166         this.headerCt.dragging = false;
122167         if (this.dragHd) {
122168             if (!this.dynamic) {
122169                 var dragHd      = this.dragHd,
122170                     gridSection = this.headerCt.up('[scrollerOwner]'),
122171                     lhsMarker   = gridSection.getLhsMarker(),
122172                     rhsMarker   = gridSection.getRhsMarker(),
122173                     currWidth   = dragHd.getWidth(),
122174                     offset      = this.tracker.getOffset('point'),
122175                     offscreen   = -9999;
122176
122177                 // hide markers
122178                 lhsMarker.setLeft(offscreen);
122179                 rhsMarker.setLeft(offscreen);
122180             }
122181             this.doResize();
122182         }
122183     },
122184
122185     doResize: function() {
122186         if (this.dragHd) {
122187             var dragHd = this.dragHd,
122188                 nextHd,
122189                 offset = this.tracker.getOffset('point');
122190
122191             // resize the dragHd
122192             if (dragHd.flex) {
122193                 delete dragHd.flex;
122194             }
122195
122196             this.headerCt.suspendLayout = true;
122197             dragHd.setWidth(this.origWidth + offset[0], false);
122198
122199             // In the case of forceFit, change the following Header width.
122200             // Then apply the two width changes by laying out the owning HeaderContainer
122201             // If HeaderContainer is configured forceFit, inhibit upstream layout notification, so that
122202             // we can also shrink the following Header by an equal amount, and *then* inform the upstream layout.
122203             if (this.headerCt.forceFit) {
122204                 nextHd = dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
122205                 if (nextHd) {
122206                     delete nextHd.flex;
122207                     nextHd.setWidth(nextHd.getWidth() - offset[0], false);
122208                 }
122209             }
122210             this.headerCt.suspendLayout = false;
122211             this.headerCt.doComponentLayout(this.headerCt.getFullWidth());
122212         }
122213     },
122214
122215     disable: function() {
122216         this.disabled = true;
122217         if (this.tracker) {
122218             this.tracker.disable();
122219         }
122220     },
122221
122222     enable: function() {
122223         this.disabled = false;
122224         if (this.tracker) {
122225             this.tracker.enable();
122226         }
122227     }
122228 });
122229 /**
122230  * The Ext.grid.plugin.RowEditing plugin injects editing at a row level for a Grid. When editing begins,
122231  * a small floating dialog will be shown for the appropriate row. Each editable column will show a field
122232  * for editing. There is a button to save or cancel all changes for the edit.
122233  *
122234  * The field that will be used for the editor is defined at the
122235  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
122236  * If an editor is not specified for a particular column then that column won't be editable and the value of
122237  * the column will be displayed.
122238  *
122239  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
122240  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
122241  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
122242  *
122243  *     @example
122244  *     Ext.create('Ext.data.Store', {
122245  *         storeId:'simpsonsStore',
122246  *         fields:['name', 'email', 'phone'],
122247  *         data: [
122248  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
122249  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
122250  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
122251  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
122252  *         ]
122253  *     });
122254  *
122255  *     Ext.create('Ext.grid.Panel', {
122256  *         title: 'Simpsons',
122257  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
122258  *         columns: [
122259  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
122260  *             {header: 'Email', dataIndex: 'email', flex:1,
122261  *                 editor: {
122262  *                     xtype: 'textfield',
122263  *                     allowBlank: false
122264  *                 }
122265  *             },
122266  *             {header: 'Phone', dataIndex: 'phone'}
122267  *         ],
122268  *         selType: 'rowmodel',
122269  *         plugins: [
122270  *             Ext.create('Ext.grid.plugin.RowEditing', {
122271  *                 clicksToEdit: 1
122272  *             })
122273  *         ],
122274  *         height: 200,
122275  *         width: 400,
122276  *         renderTo: Ext.getBody()
122277  *     });
122278  */
122279 Ext.define('Ext.grid.plugin.RowEditing', {
122280     extend: 'Ext.grid.plugin.Editing',
122281     alias: 'plugin.rowediting',
122282
122283     requires: [
122284         'Ext.grid.RowEditor'
122285     ],
122286
122287     editStyle: 'row',
122288
122289     /**
122290      * @cfg {Boolean} autoCancel
122291      * True to automatically cancel any pending changes when the row editor begins editing a new row.
122292      * False to force the user to explicitly cancel the pending changes. Defaults to true.
122293      */
122294     autoCancel: true,
122295
122296     /**
122297      * @cfg {Number} clicksToMoveEditor
122298      * The number of clicks to move the row editor to a new row while it is visible and actively editing another row.
122299      * This will default to the same value as {@link Ext.grid.plugin.Editing#clicksToEdit clicksToEdit}.
122300      */
122301
122302     /**
122303      * @cfg {Boolean} errorSummary
122304      * True to show a {@link Ext.tip.ToolTip tooltip} that summarizes all validation errors present
122305      * in the row editor. Set to false to prevent the tooltip from showing. Defaults to true.
122306      */
122307     errorSummary: true,
122308
122309     /**
122310      * @event beforeedit
122311      * Fires before row editing is triggered.
122312      *
122313      * @param {Ext.grid.plugin.Editing} editor
122314      * @param {Object} e An edit event with the following properties:
122315      *
122316      * - grid - The grid this editor is on
122317      * - view - The grid view
122318      * - store - The grid store
122319      * - record - The record being edited
122320      * - row - The grid table row
122321      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
122322      * - rowIdx - The row index that is being edited
122323      * - colIdx - The column index that initiated the edit
122324      * - cancel - Set this to true to cancel the edit or return false from your handler.
122325      */
122326     
122327     /**
122328      * @event canceledit
122329      * Fires when the user has started editing a row but then cancelled the edit
122330      * @param {Object} grid The grid
122331      */
122332     
122333     /**
122334      * @event edit
122335      * Fires after a row is edited. Usage example:
122336      *
122337      *     grid.on('edit', function(editor, e) {
122338      *         // commit the changes right after editing finished
122339      *         e.record.commit();
122340      *     };
122341      *
122342      * @param {Ext.grid.plugin.Editing} editor
122343      * @param {Object} e An edit event with the following properties:
122344      *
122345      * - grid - The grid this editor is on
122346      * - view - The grid view
122347      * - store - The grid store
122348      * - record - The record being edited
122349      * - row - The grid table row
122350      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
122351      * - rowIdx - The row index that is being edited
122352      * - colIdx - The column index that initiated the edit
122353      */
122354     /**
122355      * @event validateedit
122356      * Fires after a cell is edited, but before the value is set in the record. Return false to cancel the change. The
122357      * edit event object has the following properties
122358      *
122359      * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
122360      * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for example)
122361      * and then setting the field's new value in the Record directly:
122362      *
122363      *     grid.on('validateedit', function(editor, e) {
122364      *       var myTargetRow = 6;
122365      *
122366      *       if (e.rowIdx == myTargetRow) {
122367      *         e.cancel = true;
122368      *         e.record.data[e.field] = e.value;
122369      *       }
122370      *     });
122371      *
122372      * @param {Ext.grid.plugin.Editing} editor
122373      * @param {Object} e An edit event with the following properties:
122374      *
122375      * - grid - The grid this editor is on
122376      * - view - The grid view
122377      * - store - The grid store
122378      * - record - The record being edited
122379      * - row - The grid table row
122380      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
122381      * - rowIdx - The row index that is being edited
122382      * - colIdx - The column index that initiated the edit
122383      * - cancel - Set this to true to cancel the edit or return false from your handler.
122384      */
122385
122386     constructor: function() {
122387         var me = this;
122388         me.callParent(arguments);
122389
122390         if (!me.clicksToMoveEditor) {
122391             me.clicksToMoveEditor = me.clicksToEdit;
122392         }
122393
122394         me.autoCancel = !!me.autoCancel;
122395     },
122396
122397     /**
122398      * @private
122399      * AbstractComponent calls destroy on all its plugins at destroy time.
122400      */
122401     destroy: function() {
122402         var me = this;
122403         Ext.destroy(me.editor);
122404         me.callParent(arguments);
122405     },
122406
122407     /**
122408      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
122409      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
122410      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
122411      */
122412     startEdit: function(record, columnHeader) {
122413         var me = this,
122414             editor = me.getEditor();
122415
122416         if (me.callParent(arguments) === false) {
122417             return false;
122418         }
122419
122420         // Fire off our editor
122421         if (editor.beforeEdit() !== false) {
122422             editor.startEdit(me.context.record, me.context.column);
122423         }
122424     },
122425
122426     // private
122427     cancelEdit: function() {
122428         var me = this;
122429
122430         if (me.editing) {
122431             me.getEditor().cancelEdit();
122432             me.callParent(arguments);
122433             
122434             me.fireEvent('canceledit', me.context);
122435         }
122436     },
122437
122438     // private
122439     completeEdit: function() {
122440         var me = this;
122441
122442         if (me.editing && me.validateEdit()) {
122443             me.editing = false;
122444             me.fireEvent('edit', me.context);
122445         }
122446     },
122447
122448     // private
122449     validateEdit: function() {
122450         var me             = this,
122451             editor         = me.editor,
122452             context        = me.context,
122453             record         = context.record,
122454             newValues      = {},
122455             originalValues = {},
122456             name;
122457
122458         editor.items.each(function(item) {
122459             name = item.name;
122460
122461             newValues[name]      = item.getValue();
122462             originalValues[name] = record.get(name);
122463         });
122464
122465         Ext.apply(context, {
122466             newValues      : newValues,
122467             originalValues : originalValues
122468         });
122469
122470         return me.callParent(arguments) && me.getEditor().completeEdit();
122471     },
122472
122473     // private
122474     getEditor: function() {
122475         var me = this;
122476
122477         if (!me.editor) {
122478             me.editor = me.initEditor();
122479         }
122480         return me.editor;
122481     },
122482
122483     // private
122484     initEditor: function() {
122485         var me = this,
122486             grid = me.grid,
122487             view = me.view,
122488             headerCt = grid.headerCt;
122489
122490         return Ext.create('Ext.grid.RowEditor', {
122491             autoCancel: me.autoCancel,
122492             errorSummary: me.errorSummary,
122493             fields: headerCt.getGridColumns(),
122494             hidden: true,
122495
122496             // keep a reference..
122497             editingPlugin: me,
122498             renderTo: view.el
122499         });
122500     },
122501
122502     // private
122503     initEditTriggers: function() {
122504         var me = this,
122505             grid = me.grid,
122506             view = me.view,
122507             headerCt = grid.headerCt,
122508             moveEditorEvent = me.clicksToMoveEditor === 1 ? 'click' : 'dblclick';
122509
122510         me.callParent(arguments);
122511
122512         if (me.clicksToMoveEditor !== me.clicksToEdit) {
122513             me.mon(view, 'cell' + moveEditorEvent, me.moveEditorByClick, me);
122514         }
122515
122516         view.on('render', function() {
122517             // Column events
122518             me.mon(headerCt, {
122519                 add: me.onColumnAdd,
122520                 remove: me.onColumnRemove,
122521                 columnresize: me.onColumnResize,
122522                 columnhide: me.onColumnHide,
122523                 columnshow: me.onColumnShow,
122524                 columnmove: me.onColumnMove,
122525                 scope: me
122526             });
122527         }, me, { single: true });
122528     },
122529
122530     startEditByClick: function() {
122531         var me = this;
122532         if (!me.editing || me.clicksToMoveEditor === me.clicksToEdit) {
122533             me.callParent(arguments);
122534         }
122535     },
122536
122537     moveEditorByClick: function() {
122538         var me = this;
122539         if (me.editing) {
122540             me.superclass.startEditByClick.apply(me, arguments);
122541         }
122542     },
122543
122544     // private
122545     onColumnAdd: function(ct, column) {
122546         if (column.isHeader) {
122547             var me = this,
122548                 editor;
122549
122550             me.initFieldAccessors(column);
122551             editor = me.getEditor();
122552
122553             if (editor && editor.onColumnAdd) {
122554                 editor.onColumnAdd(column);
122555             }
122556         }
122557     },
122558
122559     // private
122560     onColumnRemove: function(ct, column) {
122561         if (column.isHeader) {
122562             var me = this,
122563                 editor = me.getEditor();
122564
122565             if (editor && editor.onColumnRemove) {
122566                 editor.onColumnRemove(column);
122567             }
122568             me.removeFieldAccessors(column);
122569         }
122570     },
122571
122572     // private
122573     onColumnResize: function(ct, column, width) {
122574         if (column.isHeader) {
122575             var me = this,
122576                 editor = me.getEditor();
122577
122578             if (editor && editor.onColumnResize) {
122579                 editor.onColumnResize(column, width);
122580             }
122581         }
122582     },
122583
122584     // private
122585     onColumnHide: function(ct, column) {
122586         // no isHeader check here since its already a columnhide event.
122587         var me = this,
122588             editor = me.getEditor();
122589
122590         if (editor && editor.onColumnHide) {
122591             editor.onColumnHide(column);
122592         }
122593     },
122594
122595     // private
122596     onColumnShow: function(ct, column) {
122597         // no isHeader check here since its already a columnshow event.
122598         var me = this,
122599             editor = me.getEditor();
122600
122601         if (editor && editor.onColumnShow) {
122602             editor.onColumnShow(column);
122603         }
122604     },
122605
122606     // private
122607     onColumnMove: function(ct, column, fromIdx, toIdx) {
122608         // no isHeader check here since its already a columnmove event.
122609         var me = this,
122610             editor = me.getEditor();
122611
122612         if (editor && editor.onColumnMove) {
122613             editor.onColumnMove(column, fromIdx, toIdx);
122614         }
122615     },
122616
122617     // private
122618     setColumnField: function(column, field) {
122619         var me = this;
122620         me.callParent(arguments);
122621         me.getEditor().setField(column.field, column);
122622     }
122623 });
122624
122625 /**
122626  * @class Ext.grid.property.Grid
122627  * @extends Ext.grid.Panel
122628  *
122629  * A specialized grid implementation intended to mimic the traditional property grid as typically seen in
122630  * development IDEs.  Each row in the grid represents a property of some object, and the data is stored
122631  * as a set of name/value pairs in {@link Ext.grid.property.Property Properties}.  Example usage:
122632  *
122633  *     @example
122634  *     Ext.create('Ext.grid.property.Grid', {
122635  *         title: 'Properties Grid',
122636  *         width: 300,
122637  *         renderTo: Ext.getBody(),
122638  *         source: {
122639  *             "(name)": "My Object",
122640  *             "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),
122641  *             "Available": false,
122642  *             "Version": .01,
122643  *             "Description": "A test object"
122644  *         }
122645  *     });
122646  */
122647 Ext.define('Ext.grid.property.Grid', {
122648
122649     extend: 'Ext.grid.Panel',
122650
122651     alias: 'widget.propertygrid',
122652
122653     alternateClassName: 'Ext.grid.PropertyGrid',
122654
122655     uses: [
122656        'Ext.grid.plugin.CellEditing',
122657        'Ext.grid.property.Store',
122658        'Ext.grid.property.HeaderContainer',
122659        'Ext.XTemplate',
122660        'Ext.grid.CellEditor',
122661        'Ext.form.field.Date',
122662        'Ext.form.field.Text',
122663        'Ext.form.field.Number'
122664     ],
122665
122666    /**
122667     * @cfg {Object} propertyNames An object containing custom property name/display name pairs.
122668     * If specified, the display name will be shown in the name column instead of the property name.
122669     */
122670
122671     /**
122672     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
122673     */
122674
122675     /**
122676     * @cfg {Object} customEditors An object containing name/value pairs of custom editor type definitions that allow
122677     * the grid to support additional types of editable fields.  By default, the grid supports strongly-typed editing
122678     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
122679     * associated with a custom input control by specifying a custom editor.  The name of the editor
122680     * type should correspond with the name of the property that will use the editor.  Example usage:
122681     * <pre><code>
122682 var grid = new Ext.grid.property.Grid({
122683
122684     // Custom editors for certain property names
122685     customEditors: {
122686         evtStart: Ext.create('Ext.form.TimeField' {selectOnFocus:true})
122687     },
122688
122689     // Displayed name for property names in the source
122690     propertyNames: {
122691         evtStart: 'Start Time'
122692     },
122693
122694     // Data object containing properties to edit
122695     source: {
122696         evtStart: '10:00 AM'
122697     }
122698 });
122699 </code></pre>
122700     */
122701
122702     /**
122703     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
122704     */
122705
122706     /**
122707     * @cfg {Object} customRenderers An object containing name/value pairs of custom renderer type definitions that allow
122708     * the grid to support custom rendering of fields.  By default, the grid supports strongly-typed rendering
122709     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
122710     * associated with the type of the value.  The name of the renderer type should correspond with the name of the property
122711     * that it will render.  Example usage:
122712     * <pre><code>
122713 var grid = Ext.create('Ext.grid.property.Grid', {
122714     customRenderers: {
122715         Available: function(v){
122716             if (v) {
122717                 return '<span style="color: green;">Yes</span>';
122718             } else {
122719                 return '<span style="color: red;">No</span>';
122720             }
122721         }
122722     },
122723     source: {
122724         Available: true
122725     }
122726 });
122727 </code></pre>
122728     */
122729
122730     /**
122731      * @cfg {String} valueField
122732      * Optional. The name of the field from the property store to use as the value field name. Defaults to <code>'value'</code>
122733      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
122734      */
122735     valueField: 'value',
122736
122737     /**
122738      * @cfg {String} nameField
122739      * Optional. The name of the field from the property store to use as the property field name. Defaults to <code>'name'</code>
122740      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
122741      */
122742     nameField: 'name',
122743
122744     /**
122745      * @cfg {Number} nameColumnWidth
122746      * Optional. Specify the width for the name column. The value column will take any remaining space. Defaults to <tt>115</tt>.
122747      */
122748
122749     // private config overrides
122750     enableColumnMove: false,
122751     columnLines: true,
122752     stripeRows: false,
122753     trackMouseOver: false,
122754     clicksToEdit: 1,
122755     enableHdMenu: false,
122756
122757     // private
122758     initComponent : function(){
122759         var me = this;
122760
122761         me.addCls(Ext.baseCSSPrefix + 'property-grid');
122762         me.plugins = me.plugins || [];
122763
122764         // Enable cell editing. Inject a custom startEdit which always edits column 1 regardless of which column was clicked.
122765         me.plugins.push(Ext.create('Ext.grid.plugin.CellEditing', {
122766             clicksToEdit: me.clicksToEdit,
122767
122768             // Inject a startEdit which always edits the value column
122769             startEdit: function(record, column) {
122770                 // Maintainer: Do not change this 'this' to 'me'! It is the CellEditing object's own scope.
122771                 return this.self.prototype.startEdit.call(this, record, me.headerCt.child('#' + me.valueField));
122772             }
122773         }));
122774
122775         me.selModel = {
122776             selType: 'cellmodel',
122777             onCellSelect: function(position) {
122778                 if (position.column != 1) {
122779                     position.column = 1;
122780                 }
122781                 return this.self.prototype.onCellSelect.call(this, position);
122782             }
122783         };
122784         me.customRenderers = me.customRenderers || {};
122785         me.customEditors = me.customEditors || {};
122786
122787         // Create a property.Store from the source object unless configured with a store
122788         if (!me.store) {
122789             me.propStore = me.store = Ext.create('Ext.grid.property.Store', me, me.source);
122790         }
122791
122792         me.store.sort('name', 'ASC');
122793         me.columns = Ext.create('Ext.grid.property.HeaderContainer', me, me.store);
122794
122795         me.addEvents(
122796             /**
122797              * @event beforepropertychange
122798              * Fires before a property value changes.  Handlers can return false to cancel the property change
122799              * (this will internally call {@link Ext.data.Model#reject} on the property's record).
122800              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
122801              * as the {@link #source} config property).
122802              * @param {String} recordId The record's id in the data store
122803              * @param {Object} value The current edited property value
122804              * @param {Object} oldValue The original property value prior to editing
122805              */
122806             'beforepropertychange',
122807             /**
122808              * @event propertychange
122809              * Fires after a property value has changed.
122810              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
122811              * as the {@link #source} config property).
122812              * @param {String} recordId The record's id in the data store
122813              * @param {Object} value The current edited property value
122814              * @param {Object} oldValue The original property value prior to editing
122815              */
122816             'propertychange'
122817         );
122818         me.callParent();
122819
122820         // Inject a custom implementation of walkCells which only goes up or down
122821         me.getView().walkCells = this.walkCells;
122822
122823         // Set up our default editor set for the 4 atomic data types
122824         me.editors = {
122825             'date'    : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Date',   {selectOnFocus: true})}),
122826             'string'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Text',   {selectOnFocus: true})}),
122827             'number'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Number', {selectOnFocus: true})}),
122828             'boolean' : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.ComboBox', {
122829                 editable: false,
122830                 store: [[ true, me.headerCt.trueText ], [false, me.headerCt.falseText ]]
122831             })})
122832         };
122833
122834         // Track changes to the data so we can fire our events.
122835         me.store.on('update', me.onUpdate, me);
122836     },
122837
122838     // private
122839     onUpdate : function(store, record, operation) {
122840         var me = this,
122841             v, oldValue;
122842
122843         if (operation == Ext.data.Model.EDIT) {
122844             v = record.get(me.valueField);
122845             oldValue = record.modified.value;
122846             if (me.fireEvent('beforepropertychange', me.source, record.getId(), v, oldValue) !== false) {
122847                 if (me.source) {
122848                     me.source[record.getId()] = v;
122849                 }
122850                 record.commit();
122851                 me.fireEvent('propertychange', me.source, record.getId(), v, oldValue);
122852             } else {
122853                 record.reject();
122854             }
122855         }
122856     },
122857
122858     // Custom implementation of walkCells which only goes up and down.
122859     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
122860         if (direction == 'left') {
122861             direction = 'up';
122862         } else if (direction == 'right') {
122863             direction = 'down';
122864         }
122865         pos = Ext.view.Table.prototype.walkCells.call(this, pos, direction, e, preventWrap, verifierFn, scope);
122866         if (!pos.column) {
122867             pos.column = 1;
122868         }
122869         return pos;
122870     },
122871
122872     // private
122873     // returns the correct editor type for the property type, or a custom one keyed by the property name
122874     getCellEditor : function(record, column) {
122875         var me = this,
122876             propName = record.get(me.nameField),
122877             val = record.get(me.valueField),
122878             editor = me.customEditors[propName];
122879
122880         // A custom editor was found. If not already wrapped with a CellEditor, wrap it, and stash it back
122881         // If it's not even a Field, just a config object, instantiate it before wrapping it.
122882         if (editor) {
122883             if (!(editor instanceof Ext.grid.CellEditor)) {
122884                 if (!(editor instanceof Ext.form.field.Base)) {
122885                     editor = Ext.ComponentManager.create(editor, 'textfield');
122886                 }
122887                 editor = me.customEditors[propName] = Ext.create('Ext.grid.CellEditor', { field: editor });
122888             }
122889         } else if (Ext.isDate(val)) {
122890             editor = me.editors.date;
122891         } else if (Ext.isNumber(val)) {
122892             editor = me.editors.number;
122893         } else if (Ext.isBoolean(val)) {
122894             editor = me.editors['boolean'];
122895         } else {
122896             editor = me.editors.string;
122897         }
122898
122899         // Give the editor a unique ID because the CellEditing plugin caches them
122900         editor.editorId = propName;
122901         return editor;
122902     },
122903
122904     beforeDestroy: function() {
122905         var me = this;
122906         me.callParent();
122907         me.destroyEditors(me.editors);
122908         me.destroyEditors(me.customEditors);
122909         delete me.source;
122910     },
122911
122912     destroyEditors: function (editors) {
122913         for (var ed in editors) {
122914             if (editors.hasOwnProperty(ed)) {
122915                 Ext.destroy(editors[ed]);
122916             }
122917         }
122918     },
122919
122920     /**
122921      * Sets the source data object containing the property data.  The data object can contain one or more name/value
122922      * pairs representing all of the properties of an object to display in the grid, and this data will automatically
122923      * be loaded into the grid's {@link #store}.  The values should be supplied in the proper data type if needed,
122924      * otherwise string type will be assumed.  If the grid already contains data, this method will replace any
122925      * existing data.  See also the {@link #source} config value.  Example usage:
122926      * <pre><code>
122927 grid.setSource({
122928     "(name)": "My Object",
122929     "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),  // date type
122930     "Available": false,  // boolean type
122931     "Version": .01,      // decimal type
122932     "Description": "A test object"
122933 });
122934 </code></pre>
122935      * @param {Object} source The data object
122936      */
122937     setSource: function(source) {
122938         this.source = source;
122939         this.propStore.setSource(source);
122940     },
122941
122942     /**
122943      * Gets the source data object containing the property data.  See {@link #setSource} for details regarding the
122944      * format of the data object.
122945      * @return {Object} The data object
122946      */
122947     getSource: function() {
122948         return this.propStore.getSource();
122949     },
122950
122951     /**
122952      * Sets the value of a property.
122953      * @param {String} prop The name of the property to set
122954      * @param {Object} value The value to test
122955      * @param {Boolean} create (Optional) True to create the property if it doesn't already exist. Defaults to <tt>false</tt>.
122956      */
122957     setProperty: function(prop, value, create) {
122958         this.propStore.setValue(prop, value, create);
122959     },
122960
122961     /**
122962      * Removes a property from the grid.
122963      * @param {String} prop The name of the property to remove
122964      */
122965     removeProperty: function(prop) {
122966         this.propStore.remove(prop);
122967     }
122968
122969     /**
122970      * @cfg store
122971      * @hide
122972      */
122973     /**
122974      * @cfg colModel
122975      * @hide
122976      */
122977     /**
122978      * @cfg cm
122979      * @hide
122980      */
122981     /**
122982      * @cfg columns
122983      * @hide
122984      */
122985 });
122986 /**
122987  * @class Ext.grid.property.HeaderContainer
122988  * @extends Ext.grid.header.Container
122989  * A custom HeaderContainer for the {@link Ext.grid.property.Grid}.  Generally it should not need to be used directly.
122990  */
122991 Ext.define('Ext.grid.property.HeaderContainer', {
122992
122993     extend: 'Ext.grid.header.Container',
122994
122995     alternateClassName: 'Ext.grid.PropertyColumnModel',
122996     
122997     nameWidth: 115,
122998
122999     // private - strings used for locale support
123000     nameText : 'Name',
123001     valueText : 'Value',
123002     dateFormat : 'm/j/Y',
123003     trueText: 'true',
123004     falseText: 'false',
123005
123006     // private
123007     nameColumnCls: Ext.baseCSSPrefix + 'grid-property-name',
123008
123009     /**
123010      * Creates new HeaderContainer.
123011      * @param {Ext.grid.property.Grid} grid The grid this store will be bound to
123012      * @param {Object} source The source data config object
123013      */
123014     constructor : function(grid, store) {
123015         var me = this;
123016         
123017         me.grid = grid;
123018         me.store = store;
123019         me.callParent([{
123020             items: [{
123021                 header: me.nameText,
123022                 width: grid.nameColumnWidth || me.nameWidth,
123023                 sortable: true,
123024                 dataIndex: grid.nameField,
123025                 renderer: Ext.Function.bind(me.renderProp, me),
123026                 itemId: grid.nameField,
123027                 menuDisabled :true,
123028                 tdCls: me.nameColumnCls
123029             }, {
123030                 header: me.valueText,
123031                 renderer: Ext.Function.bind(me.renderCell, me),
123032                 getEditor: Ext.Function.bind(me.getCellEditor, me),
123033                 flex: 1,
123034                 fixed: true,
123035                 dataIndex: grid.valueField,
123036                 itemId: grid.valueField,
123037                 menuDisabled: true
123038             }]
123039         }]);
123040     },
123041     
123042     getCellEditor: function(record){
123043         return this.grid.getCellEditor(record, this);
123044     },
123045
123046     // private
123047     // Render a property name cell
123048     renderProp : function(v) {
123049         return this.getPropertyName(v);
123050     },
123051
123052     // private
123053     // Render a property value cell
123054     renderCell : function(val, meta, rec) {
123055         var me = this,
123056             renderer = me.grid.customRenderers[rec.get(me.grid.nameField)],
123057             result = val;
123058
123059         if (renderer) {
123060             return renderer.apply(me, arguments);
123061         }
123062         if (Ext.isDate(val)) {
123063             result = me.renderDate(val);
123064         } else if (Ext.isBoolean(val)) {
123065             result = me.renderBool(val);
123066         }
123067         return Ext.util.Format.htmlEncode(result);
123068     },
123069
123070     // private
123071     renderDate : Ext.util.Format.date,
123072
123073     // private
123074     renderBool : function(bVal) {
123075         return this[bVal ? 'trueText' : 'falseText'];
123076     },
123077
123078     // private
123079     // Renders custom property names instead of raw names if defined in the Grid
123080     getPropertyName : function(name) {
123081         var pn = this.grid.propertyNames;
123082         return pn && pn[name] ? pn[name] : name;
123083     }
123084 });
123085 /**
123086  * @class Ext.grid.property.Property
123087  * A specific {@link Ext.data.Model} type that represents a name/value pair and is made to work with the
123088  * {@link Ext.grid.property.Grid}.  Typically, Properties do not need to be created directly as they can be
123089  * created implicitly by simply using the appropriate data configs either via the {@link Ext.grid.property.Grid#source}
123090  * config property or by calling {@link Ext.grid.property.Grid#setSource}.  However, if the need arises, these records
123091  * can also be created explicitly as shown below.  Example usage:
123092  * <pre><code>
123093 var rec = new Ext.grid.property.Property({
123094     name: 'birthday',
123095     value: Ext.Date.parse('17/06/1962', 'd/m/Y')
123096 });
123097 // Add record to an already populated grid
123098 grid.store.addSorted(rec);
123099 </code></pre>
123100  * @constructor
123101  * @param {Object} config A data object in the format:<pre><code>
123102 {
123103     name: [name],
123104     value: [value]
123105 }</code></pre>
123106  * The specified value's type
123107  * will be read automatically by the grid to determine the type of editor to use when displaying it.
123108  */
123109 Ext.define('Ext.grid.property.Property', {
123110     extend: 'Ext.data.Model',
123111
123112     alternateClassName: 'Ext.PropGridProperty',
123113
123114     fields: [{
123115         name: 'name',
123116         type: 'string'
123117     }, {
123118         name: 'value'
123119     }],
123120     idProperty: 'name'
123121 });
123122 /**
123123  * @class Ext.grid.property.Store
123124  * @extends Ext.data.Store
123125  * A custom {@link Ext.data.Store} for the {@link Ext.grid.property.Grid}. This class handles the mapping
123126  * between the custom data source objects supported by the grid and the {@link Ext.grid.property.Property} format
123127  * used by the {@link Ext.data.Store} base class.
123128  */
123129 Ext.define('Ext.grid.property.Store', {
123130
123131     extend: 'Ext.data.Store',
123132
123133     alternateClassName: 'Ext.grid.PropertyStore',
123134
123135     uses: ['Ext.data.reader.Reader', 'Ext.data.proxy.Proxy', 'Ext.data.ResultSet', 'Ext.grid.property.Property'],
123136
123137     /**
123138      * Creates new property store.
123139      * @param {Ext.grid.Panel} grid The grid this store will be bound to
123140      * @param {Object} source The source data config object
123141      */
123142     constructor : function(grid, source){
123143         var me = this;
123144         
123145         me.grid = grid;
123146         me.source = source;
123147         me.callParent([{
123148             data: source,
123149             model: Ext.grid.property.Property,
123150             proxy: me.getProxy()
123151         }]);
123152     },
123153
123154     // Return a singleton, customized Proxy object which configures itself with a custom Reader
123155     getProxy: function() {
123156         if (!this.proxy) {
123157             Ext.grid.property.Store.prototype.proxy = Ext.create('Ext.data.proxy.Memory', {
123158                 model: Ext.grid.property.Property,
123159                 reader: this.getReader()
123160             });
123161         }
123162         return this.proxy;
123163     },
123164
123165     // Return a singleton, customized Reader object which reads Ext.grid.property.Property records from an object.
123166     getReader: function() {
123167         if (!this.reader) {
123168             Ext.grid.property.Store.prototype.reader = Ext.create('Ext.data.reader.Reader', {
123169                 model: Ext.grid.property.Property,
123170
123171                 buildExtractors: Ext.emptyFn,
123172
123173                 read: function(dataObject) {
123174                     return this.readRecords(dataObject);
123175                 },
123176
123177                 readRecords: function(dataObject) {
123178                     var val,
123179                         propName,
123180                         result = {
123181                             records: [],
123182                             success: true
123183                         };
123184
123185                     for (propName in dataObject) {
123186                         if (dataObject.hasOwnProperty(propName)) {
123187                             val = dataObject[propName];
123188                             if (this.isEditableValue(val)) {
123189                                 result.records.push(new Ext.grid.property.Property({
123190                                     name: propName,
123191                                     value: val
123192                                 }, propName));
123193                             }
123194                         }
123195                     }
123196                     result.total = result.count = result.records.length;
123197                     return Ext.create('Ext.data.ResultSet', result);
123198                 },
123199
123200                 // private
123201                 isEditableValue: function(val){
123202                     return Ext.isPrimitive(val) || Ext.isDate(val);
123203                 }
123204             });
123205         }
123206         return this.reader;
123207     },
123208
123209     // protected - should only be called by the grid.  Use grid.setSource instead.
123210     setSource : function(dataObject) {
123211         var me = this;
123212
123213         me.source = dataObject;
123214         me.suspendEvents();
123215         me.removeAll();
123216         me.proxy.data = dataObject;
123217         me.load();
123218         me.resumeEvents();
123219         me.fireEvent('datachanged', me);
123220     },
123221
123222     // private
123223     getProperty : function(row) {
123224        return Ext.isNumber(row) ? this.getAt(row) : this.getById(row);
123225     },
123226
123227     // private
123228     setValue : function(prop, value, create){
123229         var me = this,
123230             rec = me.getRec(prop);
123231             
123232         if (rec) {
123233             rec.set('value', value);
123234             me.source[prop] = value;
123235         } else if (create) {
123236             // only create if specified.
123237             me.source[prop] = value;
123238             rec = new Ext.grid.property.Property({name: prop, value: value}, prop);
123239             me.add(rec);
123240         }
123241     },
123242
123243     // private
123244     remove : function(prop) {
123245         var rec = this.getRec(prop);
123246         if (rec) {
123247             this.callParent([rec]);
123248             delete this.source[prop];
123249         }
123250     },
123251
123252     // private
123253     getRec : function(prop) {
123254         return this.getById(prop);
123255     },
123256
123257     // protected - should only be called by the grid.  Use grid.getSource instead.
123258     getSource : function() {
123259         return this.source;
123260     }
123261 });
123262 /**
123263  * Component layout for components which maintain an inner body element which must be resized to synchronize with the
123264  * Component size.
123265  * @class Ext.layout.component.Body
123266  * @extends Ext.layout.component.Component
123267  * @private
123268  */
123269
123270 Ext.define('Ext.layout.component.Body', {
123271
123272     /* Begin Definitions */
123273
123274     alias: ['layout.body'],
123275
123276     extend: 'Ext.layout.component.Component',
123277
123278     uses: ['Ext.layout.container.Container'],
123279
123280     /* End Definitions */
123281
123282     type: 'body',
123283     
123284     onLayout: function(width, height) {
123285         var me = this,
123286             owner = me.owner;
123287
123288         // Size the Component's encapsulating element according to the dimensions
123289         me.setTargetSize(width, height);
123290
123291         // Size the Component's body element according to the content box of the encapsulating element
123292         me.setBodySize.apply(me, arguments);
123293
123294         // We need to bind to the owner whenever we do not have a user set height or width.
123295         if (owner && owner.layout && owner.layout.isLayout) {
123296             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
123297                 owner.layout.bindToOwnerCtComponent = true;
123298             }
123299             else {
123300                 owner.layout.bindToOwnerCtComponent = false;
123301             }
123302         }
123303         
123304         me.callParent(arguments);
123305     },
123306
123307     /**
123308      * @private
123309      * <p>Sizes the Component's body element to fit exactly within the content box of the Component's encapsulating element.<p>
123310      */
123311     setBodySize: function(width, height) {
123312         var me = this,
123313             owner = me.owner,
123314             frameSize = owner.frameSize,
123315             isNumber = Ext.isNumber;
123316
123317         if (isNumber(width)) {
123318             width -= owner.el.getFrameWidth('lr') - frameSize.left - frameSize.right;
123319         }
123320         if (isNumber(height)) {
123321             height -= owner.el.getFrameWidth('tb') - frameSize.top - frameSize.bottom;
123322         }
123323
123324         me.setElementSize(owner.body, width, height);
123325     }
123326 });
123327 /**
123328  * Component layout for Ext.form.FieldSet components
123329  * @class Ext.layout.component.FieldSet
123330  * @extends Ext.layout.component.Body
123331  * @private
123332  */
123333 Ext.define('Ext.layout.component.FieldSet', {
123334     extend: 'Ext.layout.component.Body',
123335     alias: ['layout.fieldset'],
123336
123337     type: 'fieldset',
123338
123339     doContainerLayout: function() {
123340         // Prevent layout/rendering of children if the fieldset is collapsed
123341         if (!this.owner.collapsed) {
123342             this.callParent();
123343         }
123344     }
123345 });
123346 /**
123347  * Component layout for tabs
123348  * @class Ext.layout.component.Tab
123349  * @extends Ext.layout.component.Button
123350  * @private
123351  */
123352 Ext.define('Ext.layout.component.Tab', {
123353
123354     alias: ['layout.tab'],
123355
123356     extend: 'Ext.layout.component.Button',
123357
123358     //type: 'button',
123359
123360     beforeLayout: function() {
123361         var me = this, dirty = me.lastClosable !== me.owner.closable;
123362
123363         if (dirty) {
123364             delete me.adjWidth;
123365         }
123366
123367         return this.callParent(arguments) || dirty;
123368     },
123369
123370     onLayout: function () {
123371         var me = this;
123372
123373         me.callParent(arguments);
123374
123375         me.lastClosable = me.owner.closable;
123376     }
123377 });
123378 /**
123379  * @private
123380  * @class Ext.layout.component.field.File
123381  * @extends Ext.layout.component.field.Field
123382  * Layout class for {@link Ext.form.field.File} fields. Adjusts the input field size to accommodate
123383  * the file picker trigger button.
123384  * @private
123385  */
123386
123387 Ext.define('Ext.layout.component.field.File', {
123388     alias: ['layout.filefield'],
123389     extend: 'Ext.layout.component.field.Field',
123390
123391     type: 'filefield',
123392
123393     sizeBodyContents: function(width, height) {
123394         var me = this,
123395             owner = me.owner;
123396
123397         if (!owner.buttonOnly) {
123398             // Decrease the field's width by the width of the button and the configured buttonMargin.
123399             // Both the text field and the button are floated left in CSS so they'll stack up side by side.
123400             me.setElementSize(owner.inputEl, Ext.isNumber(width) ? width - owner.button.getWidth() - owner.buttonMargin : width);
123401         }
123402     }
123403 });
123404 /**
123405  * @class Ext.layout.component.field.Slider
123406  * @extends Ext.layout.component.field.Field
123407  * @private
123408  */
123409
123410 Ext.define('Ext.layout.component.field.Slider', {
123411
123412     /* Begin Definitions */
123413
123414     alias: ['layout.sliderfield'],
123415
123416     extend: 'Ext.layout.component.field.Field',
123417
123418     /* End Definitions */
123419
123420     type: 'sliderfield',
123421
123422     sizeBodyContents: function(width, height) {
123423         var owner = this.owner,
123424             thumbs = owner.thumbs,
123425             length = thumbs.length,
123426             inputEl = owner.inputEl,
123427             innerEl = owner.innerEl,
123428             endEl = owner.endEl,
123429             i = 0;
123430
123431         /*
123432          * If we happen to be animating during a resize, the position of the thumb will likely be off
123433          * when the animation stops. As such, just stop any animations before syncing the thumbs.
123434          */
123435         for(; i < length; ++i) {
123436             thumbs[i].el.stopAnimation();
123437         }
123438         
123439         if (owner.vertical) {
123440             inputEl.setHeight(height);
123441             innerEl.setHeight(Ext.isNumber(height) ? height - inputEl.getPadding('t') - endEl.getPadding('b') : height);
123442         }
123443         else {
123444             inputEl.setWidth(width);
123445             innerEl.setWidth(Ext.isNumber(width) ? width - inputEl.getPadding('l') - endEl.getPadding('r') : width);
123446         }
123447         owner.syncThumbs();
123448     }
123449 });
123450
123451 /**
123452  * @class Ext.layout.container.Absolute
123453  * @extends Ext.layout.container.Anchor
123454  *
123455  * This is a layout that inherits the anchoring of {@link Ext.layout.container.Anchor} and adds the
123456  * ability for x/y positioning using the standard x and y component config options.
123457  *
123458  * This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
123459  * configuration property.  See {@link Ext.container.Container#layout} for additional details.
123460  *
123461  *     @example
123462  *     Ext.create('Ext.form.Panel', {
123463  *         title: 'Absolute Layout',
123464  *         width: 300,
123465  *         height: 275,
123466  *         layout:'absolute',
123467  *         layoutConfig: {
123468  *             // layout-specific configs go here
123469  *             //itemCls: 'x-abs-layout-item',
123470  *         },
123471  *         url:'save-form.php',
123472  *         defaultType: 'textfield',
123473  *         items: [{
123474  *             x: 10,
123475  *             y: 10,
123476  *             xtype:'label',
123477  *             text: 'Send To:'
123478  *         },{
123479  *             x: 80,
123480  *             y: 10,
123481  *             name: 'to',
123482  *             anchor:'90%'  // anchor width by percentage
123483  *         },{
123484  *             x: 10,
123485  *             y: 40,
123486  *             xtype:'label',
123487  *             text: 'Subject:'
123488  *         },{
123489  *             x: 80,
123490  *             y: 40,
123491  *             name: 'subject',
123492  *             anchor: '90%'  // anchor width by percentage
123493  *         },{
123494  *             x:0,
123495  *             y: 80,
123496  *             xtype: 'textareafield',
123497  *             name: 'msg',
123498  *             anchor: '100% 100%'  // anchor width and height
123499  *         }],
123500  *         renderTo: Ext.getBody()
123501  *     });
123502  */
123503 Ext.define('Ext.layout.container.Absolute', {
123504
123505     /* Begin Definitions */
123506
123507     alias: 'layout.absolute',
123508     extend: 'Ext.layout.container.Anchor',
123509     alternateClassName: 'Ext.layout.AbsoluteLayout',
123510
123511     /* End Definitions */
123512
123513     itemCls: Ext.baseCSSPrefix + 'abs-layout-item',
123514
123515     type: 'absolute',
123516
123517     onLayout: function() {
123518         var me = this,
123519             target = me.getTarget(),
123520             targetIsBody = target.dom === document.body;
123521
123522         // Do not set position: relative; when the absolute layout target is the body
123523         if (!targetIsBody) {
123524             target.position();
123525         }
123526         me.paddingLeft = target.getPadding('l');
123527         me.paddingTop = target.getPadding('t');
123528         me.callParent(arguments);
123529     },
123530
123531     // private
123532     adjustWidthAnchor: function(value, comp) {
123533         //return value ? value - comp.getPosition(true)[0] + this.paddingLeft: value;
123534         return value ? value - comp.getPosition(true)[0] : value;
123535     },
123536
123537     // private
123538     adjustHeightAnchor: function(value, comp) {
123539         //return value ? value - comp.getPosition(true)[1] + this.paddingTop: value;
123540         return value ? value - comp.getPosition(true)[1] : value;
123541     }
123542 });
123543 /**
123544  * @class Ext.layout.container.Accordion
123545  * @extends Ext.layout.container.VBox
123546  *
123547  * This is a layout that manages multiple Panels in an expandable accordion style such that only
123548  * **one Panel can be expanded at any given time**. Each Panel has built-in support for expanding and collapsing.
123549  *
123550  * Note: Only Ext Panels and all subclasses of Ext.panel.Panel may be used in an accordion layout Container.
123551  *
123552  *     @example
123553  *     Ext.create('Ext.panel.Panel', {
123554  *         title: 'Accordion Layout',
123555  *         width: 300,
123556  *         height: 300,
123557  *         layout:'accordion',
123558  *         defaults: {
123559  *             // applied to each contained panel
123560  *             bodyStyle: 'padding:15px'
123561  *         },
123562  *         layoutConfig: {
123563  *             // layout-specific configs go here
123564  *             titleCollapse: false,
123565  *             animate: true,
123566  *             activeOnTop: true
123567  *         },
123568  *         items: [{
123569  *             title: 'Panel 1',
123570  *             html: 'Panel content!'
123571  *         },{
123572  *             title: 'Panel 2',
123573  *             html: 'Panel content!'
123574  *         },{
123575  *             title: 'Panel 3',
123576  *             html: 'Panel content!'
123577  *         }],
123578  *         renderTo: Ext.getBody()
123579  *     });
123580  */
123581 Ext.define('Ext.layout.container.Accordion', {
123582     extend: 'Ext.layout.container.VBox',
123583     alias: ['layout.accordion'],
123584     alternateClassName: 'Ext.layout.AccordionLayout',
123585
123586     itemCls: Ext.baseCSSPrefix + 'box-item ' + Ext.baseCSSPrefix + 'accordion-item',
123587
123588     align: 'stretch',
123589
123590     /**
123591      * @cfg {Boolean} fill
123592      * True to adjust the active item's height to fill the available space in the container, false to use the
123593      * item's current height, or auto height if not explicitly set.
123594      */
123595     fill : true,
123596
123597     /**
123598      * @cfg {Boolean} autoWidth
123599      * Child Panels have their width actively managed to fit within the accordion's width.
123600      * @deprecated This config is ignored in ExtJS 4
123601      */
123602     autoWidth : true,
123603
123604     /**
123605      * @cfg {Boolean} titleCollapse
123606      * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
123607      * expand/collapse only when the toggle tool button is clicked.  When set to false,
123608      * {@link #hideCollapseTool} should be false also.
123609      */
123610     titleCollapse : true,
123611
123612     /**
123613      * @cfg {Boolean} hideCollapseTool
123614      * True to hide the contained Panels' collapse/expand toggle buttons, false to display them.
123615      * When set to true, {@link #titleCollapse} is automatically set to <code>true</code>.
123616      */
123617     hideCollapseTool : false,
123618
123619     /**
123620      * @cfg {Boolean} collapseFirst
123621      * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
123622      * in the contained Panels' title bars, false to render it last.
123623      */
123624     collapseFirst : false,
123625
123626     /**
123627      * @cfg {Boolean} animate
123628      * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
123629      * close directly with no animation. Note: The layout performs animated collapsing
123630      * and expanding, <i>not</i> the child Panels.
123631      */
123632     animate : true,
123633     /**
123634      * @cfg {Boolean} activeOnTop
123635      * Only valid when {@link #multi} is `false` and {@link #animate} is `false`.
123636      *
123637      * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
123638      * false to keep the panels in the rendered order.
123639      */
123640     activeOnTop : false,
123641     /**
123642      * @cfg {Boolean} multi
123643      * Set to <code>true</code> to enable multiple accordion items to be open at once.
123644      */
123645     multi: false,
123646
123647     constructor: function() {
123648         var me = this;
123649
123650         me.callParent(arguments);
123651
123652         // animate flag must be false during initial render phase so we don't get animations.
123653         me.initialAnimate = me.animate;
123654         me.animate = false;
123655
123656         // Child Panels are not absolutely positioned if we are not filling, so use a different itemCls.
123657         if (me.fill === false) {
123658             me.itemCls = Ext.baseCSSPrefix + 'accordion-item';
123659         }
123660     },
123661
123662     // Cannot lay out a fitting accordion before we have been allocated a height.
123663     // So during render phase, layout will not be performed.
123664     beforeLayout: function() {
123665         var me = this;
123666
123667         me.callParent(arguments);
123668         if (me.fill) {
123669             if (!(me.owner.el.dom.style.height || me.getLayoutTargetSize().height)) {
123670                 return false;
123671             }
123672         } else {
123673             me.owner.componentLayout.monitorChildren = false;
123674             me.autoSize = true;
123675             me.owner.setAutoScroll(true);
123676         }
123677     },
123678
123679     renderItems : function(items, target) {
123680         var me = this,
123681             ln = items.length,
123682             i = 0,
123683             comp,
123684             targetSize = me.getLayoutTargetSize(),
123685             renderedPanels = [];
123686
123687         for (; i < ln; i++) {
123688             comp = items[i];
123689             if (!comp.rendered) {
123690                 renderedPanels.push(comp);
123691
123692                 // Set up initial properties for Panels in an accordion.
123693                 if (me.collapseFirst) {
123694                     comp.collapseFirst = me.collapseFirst;
123695                 }
123696                 if (me.hideCollapseTool) {
123697                     comp.hideCollapseTool = me.hideCollapseTool;
123698                     comp.titleCollapse = true;
123699                 }
123700                 else if (me.titleCollapse) {
123701                     comp.titleCollapse = me.titleCollapse;
123702                 }
123703
123704                 delete comp.hideHeader;
123705                 comp.collapsible = true;
123706                 comp.title = comp.title || '&#160;';
123707
123708                 // Set initial sizes
123709                 comp.width = targetSize.width;
123710                 if (me.fill) {
123711                     delete comp.height;
123712                     delete comp.flex;
123713
123714                     // If there is an expanded item, all others must be rendered collapsed.
123715                     if (me.expandedItem !== undefined) {
123716                         comp.collapsed = true;
123717                     }
123718                     // Otherwise expand the first item with collapsed explicitly configured as false
123719                     else if (comp.hasOwnProperty('collapsed') && comp.collapsed === false) {
123720                         comp.flex = 1;
123721                         me.expandedItem = i;
123722                     } else {
123723                         comp.collapsed = true;
123724                     }
123725                     // If we are fitting, then intercept expand/collapse requests.
123726                     me.owner.mon(comp, {
123727                         show: me.onComponentShow,
123728                         beforeexpand: me.onComponentExpand,
123729                         beforecollapse: me.onComponentCollapse,
123730                         scope: me
123731                     });
123732                 } else {
123733                     delete comp.flex;
123734                     comp.animCollapse = me.initialAnimate;
123735                     comp.autoHeight = true;
123736                     comp.autoScroll = false;
123737                 }
123738                 comp.border = comp.collapsed;
123739             }
123740         }
123741
123742         // If no collapsed:false Panels found, make the first one expanded.
123743         if (ln && me.expandedItem === undefined) {
123744             me.expandedItem = 0;
123745             comp = items[0];
123746             comp.collapsed = comp.border = false;
123747             if (me.fill) {
123748                 comp.flex = 1;
123749             }
123750         }
123751
123752         // Render all Panels.
123753         me.callParent(arguments);
123754
123755         // Postprocess rendered Panels.
123756         ln = renderedPanels.length;
123757         for (i = 0; i < ln; i++) {
123758             comp = renderedPanels[i];
123759
123760             // Delete the dimension property so that our align: 'stretch' processing manages the width from here
123761             delete comp.width;
123762
123763             comp.header.addCls(Ext.baseCSSPrefix + 'accordion-hd');
123764             comp.body.addCls(Ext.baseCSSPrefix + 'accordion-body');
123765         }
123766     },
123767
123768     onLayout: function() {
123769         var me = this;
123770
123771
123772         if (me.fill) {
123773             me.callParent(arguments);
123774         } else {
123775             var targetSize = me.getLayoutTargetSize(),
123776                 items = me.getVisibleItems(),
123777                 len = items.length,
123778                 i = 0, comp;
123779
123780             for (; i < len; i++) {
123781                 comp = items[i];
123782                 if (comp.collapsed) {
123783                     items[i].setWidth(targetSize.width);
123784                 } else {
123785                     items[i].setSize(null, null);
123786                 }
123787             }
123788         }
123789         me.updatePanelClasses();
123790
123791         return me;
123792     },
123793
123794     updatePanelClasses: function() {
123795         var children = this.getLayoutItems(),
123796             ln = children.length,
123797             siblingCollapsed = true,
123798             i, child;
123799
123800         for (i = 0; i < ln; i++) {
123801             child = children[i];
123802
123803             // Fix for EXTJSIV-3724. Windows only.
123804             // Collapsing the Psnel's el to a size which only allows a single hesder to be visible, scrolls the header out of view.
123805             if (Ext.isWindows) {
123806                 child.el.dom.scrollTop = 0;
123807             }
123808
123809             if (siblingCollapsed) {
123810                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
123811             }
123812             else {
123813                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
123814             }
123815
123816             if (i + 1 == ln && child.collapsed) {
123817                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
123818             }
123819             else {
123820                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
123821             }
123822             siblingCollapsed = child.collapsed;
123823         }
123824     },
123825     
123826     animCallback: function(){
123827         Ext.Array.forEach(this.toCollapse, function(comp){
123828             comp.fireEvent('collapse', comp);
123829         });
123830         
123831         Ext.Array.forEach(this.toExpand, function(comp){
123832             comp.fireEvent('expand', comp);
123833         });    
123834     },
123835     
123836     setupEvents: function(){
123837         this.toCollapse = [];
123838         this.toExpand = [];    
123839     },
123840
123841     // When a Component expands, adjust the heights of the other Components to be just enough to accommodate
123842     // their headers.
123843     // The expanded Component receives the only flex value, and so gets all remaining space.
123844     onComponentExpand: function(toExpand) {
123845         var me = this,
123846             it = me.owner.items.items,
123847             len = it.length,
123848             i = 0,
123849             comp;
123850
123851         me.setupEvents();
123852         for (; i < len; i++) {
123853             comp = it[i];
123854             if (comp === toExpand && comp.collapsed) {
123855                 me.setExpanded(comp);
123856             } else if (!me.multi && (comp.rendered && comp.header.rendered && comp !== toExpand && !comp.collapsed)) {
123857                 me.setCollapsed(comp);
123858             }
123859         }
123860
123861         me.animate = me.initialAnimate;
123862         if (me.activeOnTop) {
123863             // insert will trigger a layout
123864             me.owner.insert(0, toExpand); 
123865         } else {
123866             me.layout();
123867         }
123868         me.animate = false;
123869         return false;
123870     },
123871
123872     onComponentCollapse: function(comp) {
123873         var me = this,
123874             toExpand = comp.next() || comp.prev(),
123875             expanded = me.multi ? me.owner.query('>panel:not([collapsed])') : [];
123876
123877         me.setupEvents();
123878         // If we are allowing multi, and the "toCollapse" component is NOT the only expanded Component,
123879         // then ask the box layout to collapse it to its header.
123880         if (me.multi) {
123881             me.setCollapsed(comp);
123882
123883             // If the collapsing Panel is the only expanded one, expand the following Component.
123884             // All this is handling fill: true, so there must be at least one expanded,
123885             if (expanded.length === 1 && expanded[0] === comp) {
123886                 me.setExpanded(toExpand);
123887             }
123888
123889             me.animate = me.initialAnimate;
123890             me.layout();
123891             me.animate = false;
123892         }
123893         // Not allowing multi: expand the next sibling if possible, prev sibling if we collapsed the last
123894         else if (toExpand) {
123895             me.onComponentExpand(toExpand);
123896         }
123897         return false;
123898     },
123899
123900     onComponentShow: function(comp) {
123901         // Showing a Component means that you want to see it, so expand it.
123902         this.onComponentExpand(comp);
123903     },
123904
123905     setCollapsed: function(comp) {
123906         var otherDocks = comp.getDockedItems(),
123907             dockItem,
123908             len = otherDocks.length,
123909             i = 0;
123910
123911         // Hide all docked items except the header
123912         comp.hiddenDocked = [];
123913         for (; i < len; i++) {
123914             dockItem = otherDocks[i];
123915             if ((dockItem !== comp.header) && !dockItem.hidden) {
123916                 dockItem.hidden = true;
123917                 comp.hiddenDocked.push(dockItem);
123918             }
123919         }
123920         comp.addCls(comp.collapsedCls);
123921         comp.header.addCls(comp.collapsedHeaderCls);
123922         comp.height = comp.header.getHeight();
123923         comp.el.setHeight(comp.height);
123924         comp.collapsed = true;
123925         delete comp.flex;
123926         if (this.initialAnimate) {
123927             this.toCollapse.push(comp);
123928         } else {
123929             comp.fireEvent('collapse', comp);
123930         }
123931         if (comp.collapseTool) {
123932             comp.collapseTool.setType('expand-' + comp.getOppositeDirection(comp.collapseDirection));
123933         }
123934     },
123935
123936     setExpanded: function(comp) {
123937         var otherDocks = comp.hiddenDocked,
123938             len = otherDocks ? otherDocks.length : 0,
123939             i = 0;
123940
123941         // Show temporarily hidden docked items
123942         for (; i < len; i++) {
123943             otherDocks[i].show();
123944         }
123945
123946         // If it was an initial native collapse which hides the body
123947         if (!comp.body.isVisible()) {
123948             comp.body.show();
123949         }
123950         delete comp.collapsed;
123951         delete comp.height;
123952         delete comp.componentLayout.lastComponentSize;
123953         comp.suspendLayout = false;
123954         comp.flex = 1;
123955         comp.removeCls(comp.collapsedCls);
123956         comp.header.removeCls(comp.collapsedHeaderCls);
123957          if (this.initialAnimate) {
123958             this.toExpand.push(comp);
123959         } else {
123960             comp.fireEvent('expand', comp);
123961         }
123962         if (comp.collapseTool) {
123963             comp.collapseTool.setType('collapse-' + comp.collapseDirection);
123964         }
123965         comp.setAutoScroll(comp.initialConfig.autoScroll);
123966     }
123967 });
123968 /**
123969  * This class functions between siblings of a {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox}
123970  * layout to resize both immediate siblings.
123971  *
123972  * By default it will set the size of both siblings. <b>One</b> of the siblings may be configured with
123973  * `{@link Ext.Component#maintainFlex maintainFlex}: true` which will cause it not to receive a new size explicitly, but to be resized
123974  * by the layout.
123975  *
123976  * A Splitter may be configured to show a centered mini-collapse tool orientated to collapse the {@link #collapseTarget}.
123977  * The Splitter will then call that sibling Panel's {@link Ext.panel.Panel#collapse collapse} or {@link Ext.panel.Panel#expand expand} method
123978  * to perform the appropriate operation (depending on the sibling collapse state). To create the mini-collapse tool but take care
123979  * of collapsing yourself, configure the splitter with <code>{@link #performCollapse} false</code>.
123980  */
123981 Ext.define('Ext.resizer.Splitter', {
123982     extend: 'Ext.Component',
123983     requires: ['Ext.XTemplate'],
123984     uses: ['Ext.resizer.SplitterTracker'],
123985     alias: 'widget.splitter',
123986
123987     renderTpl: [
123988         '<tpl if="collapsible===true">',
123989             '<div id="{id}-collapseEl" class="', Ext.baseCSSPrefix, 'collapse-el ',
123990                     Ext.baseCSSPrefix, 'layout-split-{collapseDir}">&nbsp;</div>',
123991         '</tpl>'
123992     ],
123993
123994     baseCls: Ext.baseCSSPrefix + 'splitter',
123995     collapsedClsInternal: Ext.baseCSSPrefix + 'splitter-collapsed',
123996
123997     /**
123998      * @cfg {Boolean} collapsible
123999      * <code>true</code> to show a mini-collapse tool in the Splitter to toggle expand and collapse on the {@link #collapseTarget} Panel.
124000      * Defaults to the {@link Ext.panel.Panel#collapsible collapsible} setting of the Panel.
124001      */
124002     collapsible: false,
124003
124004     /**
124005      * @cfg {Boolean} performCollapse
124006      * <p>Set to <code>false</code> to prevent this Splitter's mini-collapse tool from managing the collapse
124007      * state of the {@link #collapseTarget}.</p>
124008      */
124009
124010     /**
124011      * @cfg {Boolean} collapseOnDblClick
124012      * <code>true</code> to enable dblclick to toggle expand and collapse on the {@link #collapseTarget} Panel.
124013      */
124014     collapseOnDblClick: true,
124015
124016     /**
124017      * @cfg {Number} defaultSplitMin
124018      * Provides a default minimum width or height for the two components
124019      * that the splitter is between.
124020      */
124021     defaultSplitMin: 40,
124022
124023     /**
124024      * @cfg {Number} defaultSplitMax
124025      * Provides a default maximum width or height for the two components
124026      * that the splitter is between.
124027      */
124028     defaultSplitMax: 1000,
124029
124030     /**
124031      * @cfg {String} collapsedCls
124032      * A class to add to the splitter when it is collapsed. See {@link #collapsible}.
124033      */
124034
124035     width: 5,
124036     height: 5,
124037
124038     /**
124039      * @cfg {String/Ext.panel.Panel} collapseTarget
124040      * <p>A string describing the relative position of the immediate sibling Panel to collapse. May be 'prev' or 'next' (Defaults to 'next')</p>
124041      * <p>Or the immediate sibling Panel to collapse.</p>
124042      * <p>The orientation of the mini-collapse tool will be inferred from this setting.</p>
124043      * <p><b>Note that only Panels may be collapsed.</b></p>
124044      */
124045     collapseTarget: 'next',
124046
124047     /**
124048      * @property orientation
124049      * @type String
124050      * Orientation of this Splitter. <code>'vertical'</code> when used in an hbox layout, <code>'horizontal'</code>
124051      * when used in a vbox layout.
124052      */
124053
124054     onRender: function() {
124055         var me = this,
124056             target = me.getCollapseTarget(),
124057             collapseDir = me.getCollapseDirection();
124058
124059         Ext.applyIf(me.renderData, {
124060             collapseDir: collapseDir,
124061             collapsible: me.collapsible || target.collapsible
124062         });
124063
124064         me.addChildEls('collapseEl');
124065
124066         this.callParent(arguments);
124067
124068         // Add listeners on the mini-collapse tool unless performCollapse is set to false
124069         if (me.performCollapse !== false) {
124070             if (me.renderData.collapsible) {
124071                 me.mon(me.collapseEl, 'click', me.toggleTargetCmp, me);
124072             }
124073             if (me.collapseOnDblClick) {
124074                 me.mon(me.el, 'dblclick', me.toggleTargetCmp, me);
124075             }
124076         }
124077
124078         // Ensure the mini collapse icon is set to the correct direction when the target is collapsed/expanded by any means
124079         me.mon(target, 'collapse', me.onTargetCollapse, me);
124080         me.mon(target, 'expand', me.onTargetExpand, me);
124081
124082         me.el.addCls(me.baseCls + '-' + me.orientation);
124083         me.el.unselectable();
124084
124085         me.tracker = Ext.create('Ext.resizer.SplitterTracker', {
124086             el: me.el
124087         });
124088
124089         // Relay the most important events to our owner (could open wider later):
124090         me.relayEvents(me.tracker, [ 'beforedragstart', 'dragstart', 'dragend' ]);
124091     },
124092
124093     getCollapseDirection: function() {
124094         var me = this,
124095             idx,
124096             type = me.ownerCt.layout.type;
124097
124098         // Avoid duplication of string tests.
124099         // Create a two bit truth table of the configuration of the Splitter:
124100         // Collapse Target | orientation
124101         //        0              0             = next, horizontal
124102         //        0              1             = next, vertical
124103         //        1              0             = prev, horizontal
124104         //        1              1             = prev, vertical
124105         if (me.collapseTarget.isComponent) {
124106             idx = Number(me.ownerCt.items.indexOf(me.collapseTarget) == me.ownerCt.items.indexOf(me) - 1) << 1 | Number(type == 'hbox');
124107         } else {
124108             idx = Number(me.collapseTarget == 'prev') << 1 | Number(type == 'hbox');
124109         }
124110
124111         // Read the data out the truth table
124112         me.orientation = ['horizontal', 'vertical'][idx & 1];
124113         return ['bottom', 'right', 'top', 'left'][idx];
124114     },
124115
124116     getCollapseTarget: function() {
124117         var me = this;
124118
124119         return me.collapseTarget.isComponent ? me.collapseTarget : me.collapseTarget == 'prev' ? me.previousSibling() : me.nextSibling();
124120     },
124121
124122     onTargetCollapse: function(target) {
124123         this.el.addCls([this.collapsedClsInternal, this.collapsedCls]);
124124     },
124125
124126     onTargetExpand: function(target) {
124127         this.el.removeCls([this.collapsedClsInternal, this.collapsedCls]);
124128     },
124129
124130     toggleTargetCmp: function(e, t) {
124131         var cmp = this.getCollapseTarget();
124132
124133         if (cmp.isVisible()) {
124134             // restore
124135             if (cmp.collapsed) {
124136                 cmp.expand(cmp.animCollapse);
124137             // collapse
124138             } else {
124139                 cmp.collapse(this.renderData.collapseDir, cmp.animCollapse);
124140             }
124141         }
124142     },
124143
124144     /*
124145      * Work around IE bug. %age margins do not get recalculated on element resize unless repaint called.
124146      */
124147     setSize: function() {
124148         var me = this;
124149         me.callParent(arguments);
124150         if (Ext.isIE) {
124151             me.el.repaint();
124152         }
124153     }
124154 });
124155
124156 /**
124157  * This is a multi-pane, application-oriented UI layout style that supports multiple nested panels, automatic bars
124158  * between regions and built-in {@link Ext.panel.Panel#collapsible expanding and collapsing} of regions.
124159  *
124160  * This class is intended to be extended or created via the `layout:'border'` {@link Ext.container.Container#layout}
124161  * config, and should generally not need to be created directly via the new keyword.
124162  *
124163  *     @example
124164  *     Ext.create('Ext.panel.Panel', {
124165  *         width: 500,
124166  *         height: 400,
124167  *         title: 'Border Layout',
124168  *         layout: 'border',
124169  *         items: [{
124170  *             title: 'South Region is resizable',
124171  *             region: 'south',     // position for region
124172  *             xtype: 'panel',
124173  *             height: 100,
124174  *             split: true,         // enable resizing
124175  *             margins: '0 5 5 5'
124176  *         },{
124177  *             // xtype: 'panel' implied by default
124178  *             title: 'West Region is collapsible',
124179  *             region:'west',
124180  *             xtype: 'panel',
124181  *             margins: '5 0 0 5',
124182  *             width: 200,
124183  *             collapsible: true,   // make collapsible
124184  *             id: 'west-region-container',
124185  *             layout: 'fit'
124186  *         },{
124187  *             title: 'Center Region',
124188  *             region: 'center',     // center region is required, no width/height specified
124189  *             xtype: 'panel',
124190  *             layout: 'fit',
124191  *             margins: '5 5 0 0'
124192  *         }],
124193  *         renderTo: Ext.getBody()
124194  *     });
124195  *
124196  * # Notes
124197  *
124198  * - Any Container using the Border layout **must** have a child item with `region:'center'`.
124199  *   The child item in the center region will always be resized to fill the remaining space
124200  *   not used by the other regions in the layout.
124201  *
124202  * - Any child items with a region of `west` or `east` may be configured with either an initial
124203  *   `width`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage width
124204  *   **string** (Which is simply divided by 100 and used as a flex value).
124205  *   The 'center' region has a flex value of `1`.
124206  *
124207  * - Any child items with a region of `north` or `south` may be configured with either an initial
124208  *   `height`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage height
124209  *   **string** (Which is simply divided by 100 and used as a flex value).
124210  *   The 'center' region has a flex value of `1`.
124211  *
124212  * - The regions of a BorderLayout are **fixed at render time** and thereafter, its child
124213  *   Components may not be removed or added**. To add/remove Components within a BorderLayout,
124214  *   have them wrapped by an additional Container which is directly managed by the BorderLayout.
124215  *   If the region is to be collapsible, the Container used directly by the BorderLayout manager
124216  *   should be a Panel. In the following example a Container (an Ext.panel.Panel) is added to
124217  *   the west region:
124218  *
124219  *       wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
124220  *       wrc.{@link Ext.container.Container#removeAll removeAll}();
124221  *       wrc.{@link Ext.container.Container#add add}({
124222  *           title: 'Added Panel',
124223  *           html: 'Some content'
124224  *       });
124225  *
124226  * - **There is no BorderLayout.Region class in ExtJS 4.0+**
124227  */
124228 Ext.define('Ext.layout.container.Border', {
124229
124230     alias: ['layout.border'],
124231     extend: 'Ext.layout.container.Container',
124232     requires: ['Ext.resizer.Splitter', 'Ext.container.Container', 'Ext.fx.Anim'],
124233     alternateClassName: 'Ext.layout.BorderLayout',
124234
124235     targetCls: Ext.baseCSSPrefix + 'border-layout-ct',
124236
124237     itemCls: Ext.baseCSSPrefix + 'border-item',
124238
124239     bindToOwnerCtContainer: true,
124240
124241     percentageRe: /(\d+)%/,
124242
124243     slideDirection: {
124244         north: 't',
124245         south: 'b',
124246         west: 'l',
124247         east: 'r'
124248     },
124249
124250     constructor: function(config) {
124251         this.initialConfig = config;
124252         this.callParent(arguments);
124253     },
124254
124255     onLayout: function() {
124256         var me = this;
124257         if (!me.borderLayoutInitialized) {
124258             me.initializeBorderLayout();
124259         }
124260
124261         // Delegate this operation to the shadow "V" or "H" box layout, and then down to any embedded layout.
124262         me.fixHeightConstraints();
124263         me.shadowLayout.onLayout();
124264         if (me.embeddedContainer) {
124265             me.embeddedContainer.layout.onLayout();
124266         }
124267
124268         // If the panel was originally configured with collapsed: true, it will have
124269         // been initialized with a "borderCollapse" flag: Collapse it now before the first layout.
124270         if (!me.initialCollapsedComplete) {
124271             Ext.iterate(me.regions, function(name, region){
124272                 if (region.borderCollapse) {
124273                     me.onBeforeRegionCollapse(region, region.collapseDirection, false, 0);
124274                 }
124275             });
124276             me.initialCollapsedComplete = true;
124277         }
124278     },
124279
124280     isValidParent : function(item, target, position) {
124281         if (!this.borderLayoutInitialized) {
124282             this.initializeBorderLayout();
124283         }
124284
124285         // Delegate this operation to the shadow "V" or "H" box layout.
124286         return this.shadowLayout.isValidParent(item, target, position);
124287     },
124288
124289     beforeLayout: function() {
124290         if (!this.borderLayoutInitialized) {
124291             this.initializeBorderLayout();
124292         }
124293
124294         // Delegate this operation to the shadow "V" or "H" box layout.
124295         this.shadowLayout.beforeLayout();
124296
124297         // note: don't call base because that does a renderItems again
124298     },
124299
124300     renderItems: function(items, target) {
124301         Ext.Error.raise('This should not be called');
124302     },
124303
124304     renderItem: function(item) {
124305         Ext.Error.raise('This should not be called');
124306     },
124307
124308     renderChildren: function() {
124309         if (!this.borderLayoutInitialized) {
124310             this.initializeBorderLayout();
124311         }
124312
124313         this.shadowLayout.renderChildren();
124314     },
124315
124316     /*
124317      * Gathers items for a layout operation. Injected into child Box layouts through configuration.
124318      * We must not include child items which are floated over the layout (are primed with a slide out animation)
124319      */
124320     getVisibleItems: function() {
124321         return Ext.ComponentQuery.query(':not([slideOutAnim])', this.callParent(arguments));
124322     },
124323
124324     initializeBorderLayout: function() {
124325         var me = this,
124326             i = 0,
124327             items = me.getLayoutItems(),
124328             ln = items.length,
124329             regions = (me.regions = {}),
124330             vBoxItems = [],
124331             hBoxItems = [],
124332             horizontalFlex = 0,
124333             verticalFlex = 0,
124334             comp, percentage;
124335
124336         // Map of Splitters for each region
124337         me.splitters = {};
124338
124339         // Map of regions
124340         for (; i < ln; i++) {
124341             comp = items[i];
124342             regions[comp.region] = comp;
124343
124344             // Intercept collapsing to implement showing an alternate Component as a collapsed placeholder
124345             if (comp.region != 'center' && comp.collapsible && comp.collapseMode != 'header') {
124346
124347                 // This layout intercepts any initial collapsed state. Panel must not do this itself.
124348                 comp.borderCollapse = comp.collapsed;
124349                 comp.collapsed = false;
124350
124351                 comp.on({
124352                     beforecollapse: me.onBeforeRegionCollapse,
124353                     beforeexpand: me.onBeforeRegionExpand,
124354                     destroy: me.onRegionDestroy,
124355                     scope: me
124356                 });
124357                 me.setupState(comp);
124358             }
124359         }
124360         if (!regions.center) {
124361             Ext.Error.raise("You must specify a center region when defining a BorderLayout.");
124362         }
124363         comp = regions.center;
124364         if (!comp.flex) {
124365             comp.flex = 1;
124366         }
124367         delete comp.width;
124368         comp.maintainFlex = true;
124369
124370         // Begin the VBox and HBox item list.
124371         comp = regions.west;
124372         if (comp) {
124373             comp.collapseDirection = Ext.Component.DIRECTION_LEFT;
124374             hBoxItems.push(comp);
124375             if (comp.split) {
124376                 hBoxItems.push(me.splitters.west = me.createSplitter(comp));
124377             }
124378             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
124379             if (percentage) {
124380                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
124381                 delete comp.width;
124382             }
124383         }
124384         comp = regions.north;
124385         if (comp) {
124386             comp.collapseDirection = Ext.Component.DIRECTION_TOP;
124387             vBoxItems.push(comp);
124388             if (comp.split) {
124389                 vBoxItems.push(me.splitters.north = me.createSplitter(comp));
124390             }
124391             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
124392             if (percentage) {
124393                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
124394                 delete comp.height;
124395             }
124396         }
124397
124398         // Decide into which Collection the center region goes.
124399         if (regions.north || regions.south) {
124400             if (regions.east || regions.west) {
124401
124402                 // Create the embedded center. Mark it with the region: 'center' property so that it can be identified as the center.
124403                 vBoxItems.push(me.embeddedContainer = Ext.create('Ext.container.Container', {
124404                     xtype: 'container',
124405                     region: 'center',
124406                     id: me.owner.id + '-embedded-center',
124407                     cls: Ext.baseCSSPrefix + 'border-item',
124408                     flex: regions.center.flex,
124409                     maintainFlex: true,
124410                     layout: {
124411                         type: 'hbox',
124412                         align: 'stretch',
124413                         getVisibleItems: me.getVisibleItems
124414                     }
124415                 }));
124416                 hBoxItems.push(regions.center);
124417             }
124418             // No east or west: the original center goes straight into the vbox
124419             else {
124420                 vBoxItems.push(regions.center);
124421             }
124422         }
124423         // If we have no north or south, then the center is part of the HBox items
124424         else {
124425             hBoxItems.push(regions.center);
124426         }
124427
124428         // Finish off the VBox and HBox item list.
124429         comp = regions.south;
124430         if (comp) {
124431             comp.collapseDirection = Ext.Component.DIRECTION_BOTTOM;
124432             if (comp.split) {
124433                 vBoxItems.push(me.splitters.south = me.createSplitter(comp));
124434             }
124435             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
124436             if (percentage) {
124437                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
124438                 delete comp.height;
124439             }
124440             vBoxItems.push(comp);
124441         }
124442         comp = regions.east;
124443         if (comp) {
124444             comp.collapseDirection = Ext.Component.DIRECTION_RIGHT;
124445             if (comp.split) {
124446                 hBoxItems.push(me.splitters.east = me.createSplitter(comp));
124447             }
124448             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
124449             if (percentage) {
124450                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
124451                 delete comp.width;
124452             }
124453             hBoxItems.push(comp);
124454         }
124455
124456         // Create the injected "items" collections for the Containers.
124457         // If we have north or south, then the shadow Container will be a VBox.
124458         // If there are also east or west regions, its center will be a shadow HBox.
124459         // If there are *only* east or west regions, then the shadow layout will be an HBox (or Fit).
124460         if (regions.north || regions.south) {
124461
124462             me.shadowContainer = Ext.create('Ext.container.Container', {
124463                 ownerCt: me.owner,
124464                 el: me.getTarget(),
124465                 layout: Ext.applyIf({
124466                     type: 'vbox',
124467                     align: 'stretch',
124468                     getVisibleItems: me.getVisibleItems
124469                 }, me.initialConfig)
124470             });
124471             me.createItems(me.shadowContainer, vBoxItems);
124472
124473             // Allow the Splitters to orientate themselves
124474             if (me.splitters.north) {
124475                 me.splitters.north.ownerCt = me.shadowContainer;
124476             }
124477             if (me.splitters.south) {
124478                 me.splitters.south.ownerCt = me.shadowContainer;
124479             }
124480
124481             // Inject items into the HBox Container if there is one - if there was an east or west.
124482             if (me.embeddedContainer) {
124483                 me.embeddedContainer.ownerCt = me.shadowContainer;
124484                 me.createItems(me.embeddedContainer, hBoxItems);
124485
124486                 // Allow the Splitters to orientate themselves
124487                 if (me.splitters.east) {
124488                     me.splitters.east.ownerCt = me.embeddedContainer;
124489                 }
124490                 if (me.splitters.west) {
124491                     me.splitters.west.ownerCt = me.embeddedContainer;
124492                 }
124493
124494                 // These spliiters need to be constrained by components one-level below
124495                 // the component in their vobx. We update the min/maxHeight on the helper
124496                 // (embeddedContainer) prior to starting the split/drag. This has to be
124497                 // done on-the-fly to allow min/maxHeight of the E/C/W regions to be set
124498                 // dynamically.
124499                 Ext.each([me.splitters.north, me.splitters.south], function (splitter) {
124500                     if (splitter) {
124501                         splitter.on('beforedragstart', me.fixHeightConstraints, me);
124502                     }
124503                 });
124504
124505                 // The east or west region wanted a percentage
124506                 if (horizontalFlex) {
124507                     regions.center.flex -= horizontalFlex;
124508                 }
124509                 // The north or south region wanted a percentage
124510                 if (verticalFlex) {
124511                     me.embeddedContainer.flex -= verticalFlex;
124512                 }
124513             } else {
124514                 // The north or south region wanted a percentage
124515                 if (verticalFlex) {
124516                     regions.center.flex -= verticalFlex;
124517                 }
124518             }
124519         }
124520         // If we have no north or south, then there's only one Container, and it's
124521         // an HBox, or, if only a center region was specified, a Fit.
124522         else {
124523             me.shadowContainer = Ext.create('Ext.container.Container', {
124524                 ownerCt: me.owner,
124525                 el: me.getTarget(),
124526                 layout: Ext.applyIf({
124527                     type: (hBoxItems.length == 1) ? 'fit' : 'hbox',
124528                     align: 'stretch'
124529                 }, me.initialConfig)
124530             });
124531             me.createItems(me.shadowContainer, hBoxItems);
124532
124533             // Allow the Splitters to orientate themselves
124534             if (me.splitters.east) {
124535                 me.splitters.east.ownerCt = me.shadowContainer;
124536             }
124537             if (me.splitters.west) {
124538                 me.splitters.west.ownerCt = me.shadowContainer;
124539             }
124540
124541             // The east or west region wanted a percentage
124542             if (horizontalFlex) {
124543                 regions.center.flex -= verticalFlex;
124544             }
124545         }
124546
124547         // Create upward links from the region Components to their shadow ownerCts
124548         for (i = 0, items = me.shadowContainer.items.items, ln = items.length; i < ln; i++) {
124549             items[i].shadowOwnerCt = me.shadowContainer;
124550         }
124551         if (me.embeddedContainer) {
124552             for (i = 0, items = me.embeddedContainer.items.items, ln = items.length; i < ln; i++) {
124553                 items[i].shadowOwnerCt = me.embeddedContainer;
124554             }
124555         }
124556
124557         // This is the layout that we delegate all operations to
124558         me.shadowLayout = me.shadowContainer.getLayout();
124559
124560         me.borderLayoutInitialized = true;
124561     },
124562
124563     setupState: function(comp){
124564         var getState = comp.getState;
124565         comp.getState = function(){
124566             // call the original getState
124567             var state = getState.call(comp) || {},
124568                 region = comp.region;
124569
124570             state.collapsed = !!comp.collapsed;
124571             if (region == 'west' || region == 'east') {
124572                 state.width = comp.getWidth();
124573             } else {
124574                 state.height = comp.getHeight();
124575             }
124576             return state;
124577         };
124578         comp.addStateEvents(['collapse', 'expand', 'resize']);
124579     },
124580
124581     /**
124582      * Create the items collection for our shadow/embedded containers
124583      * @private
124584      */
124585     createItems: function(container, items){
124586         // Have to inject an items Collection *after* construction.
124587         // The child items of the shadow layout must retain their original, user-defined ownerCt
124588         delete container.items;
124589         container.initItems();
124590         container.items.addAll(items);
124591     },
124592
124593     // Private
124594     // Create a splitter for a child of the layout.
124595     createSplitter: function(comp) {
124596         var me = this,
124597             interceptCollapse = (comp.collapseMode != 'header'),
124598             resizer;
124599
124600         resizer = Ext.create('Ext.resizer.Splitter', {
124601             hidden: !!comp.hidden,
124602             collapseTarget: comp,
124603             performCollapse: !interceptCollapse,
124604             listeners: interceptCollapse ? {
124605                 click: {
124606                     fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
124607                     element: 'collapseEl'
124608                 }
124609             } : null
124610         });
124611
124612         // Mini collapse means that the splitter is the placeholder Component
124613         if (comp.collapseMode == 'mini') {
124614             comp.placeholder = resizer;
124615             resizer.collapsedCls = comp.collapsedCls;
124616         }
124617
124618         // Arrange to hide/show a region's associated splitter when the region is hidden/shown
124619         comp.on({
124620             hide: me.onRegionVisibilityChange,
124621             show: me.onRegionVisibilityChange,
124622             scope: me
124623         });
124624         return resizer;
124625     },
124626
124627     // Private
124628     // Propagates the min/maxHeight values from the inner hbox items to its container.
124629     fixHeightConstraints: function () {
124630         var me = this,
124631             ct = me.embeddedContainer,
124632             maxHeight = 1e99, minHeight = -1;
124633
124634         if (!ct) {
124635             return;
124636         }
124637
124638         ct.items.each(function (item) {
124639             if (Ext.isNumber(item.maxHeight)) {
124640                 maxHeight = Math.max(maxHeight, item.maxHeight);
124641             }
124642             if (Ext.isNumber(item.minHeight)) {
124643                 minHeight = Math.max(minHeight, item.minHeight);
124644             }
124645         });
124646
124647         ct.maxHeight = maxHeight;
124648         ct.minHeight = minHeight;
124649     },
124650
124651     // Hide/show a region's associated splitter when the region is hidden/shown
124652     onRegionVisibilityChange: function(comp){
124653         this.splitters[comp.region][comp.hidden ? 'hide' : 'show']();
124654         this.layout();
124655     },
124656
124657     // Called when a splitter mini-collapse tool is clicked on.
124658     // The listener is only added if this layout is controlling collapsing,
124659     // not if the component's collapseMode is 'mini' or 'header'.
124660     onSplitterCollapseClick: function(comp) {
124661         if (comp.collapsed) {
124662             this.onPlaceHolderToolClick(null, null, null, {client: comp});
124663         } else {
124664             comp.collapse();
124665         }
124666     },
124667
124668     /**
124669      * Return the {@link Ext.panel.Panel#placeholder placeholder} Component to which the passed child Panel of the
124670      * layout will collapse. By default, this will be a {@link Ext.panel.Header Header} component (Docked to the
124671      * appropriate border). See {@link Ext.panel.Panel#placeholder placeholder}. config to customize this.
124672      *
124673      * **Note that this will be a fully instantiated Component, but will only be _rendered_ when the Panel is first
124674      * collapsed.**
124675      * @param {Ext.panel.Panel} panel The child Panel of the layout for which to return the {@link
124676      * Ext.panel.Panel#placeholder placeholder}.
124677      * @return {Ext.Component} The Panel's {@link Ext.panel.Panel#placeholder placeholder} unless the {@link
124678      * Ext.panel.Panel#collapseMode collapseMode} is `'header'`, in which case _undefined_ is returned.
124679      */
124680     getPlaceholder: function(comp) {
124681         var me = this,
124682             placeholder = comp.placeholder,
124683             shadowContainer = comp.shadowOwnerCt,
124684             shadowLayout = shadowContainer.layout,
124685             oppositeDirection = Ext.panel.Panel.prototype.getOppositeDirection(comp.collapseDirection),
124686             horiz = (comp.region == 'north' || comp.region == 'south');
124687
124688         // No placeholder if the collapse mode is not the Border layout default
124689         if (comp.collapseMode == 'header') {
124690             return;
124691         }
124692
124693         // Provide a replacement Container with an expand tool
124694         if (!placeholder) {
124695             if (comp.collapseMode == 'mini') {
124696                 placeholder = Ext.create('Ext.resizer.Splitter', {
124697                     id: 'collapse-placeholder-' + comp.id,
124698                     collapseTarget: comp,
124699                     performCollapse: false,
124700                     listeners: {
124701                         click: {
124702                             fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
124703                             element: 'collapseEl'
124704                         }
124705                     }
124706                 });
124707                 placeholder.addCls(placeholder.collapsedCls);
124708             } else {
124709                 placeholder = {
124710                     id: 'collapse-placeholder-' + comp.id,
124711                     margins: comp.initialConfig.margins || Ext.getClass(comp).prototype.margins,
124712                     xtype: 'header',
124713                     orientation: horiz ? 'horizontal' : 'vertical',
124714                     title: comp.title,
124715                     textCls: comp.headerTextCls,
124716                     iconCls: comp.iconCls,
124717                     baseCls: comp.baseCls + '-header',
124718                     ui: comp.ui,
124719                     indicateDrag: comp.draggable,
124720                     cls: Ext.baseCSSPrefix + 'region-collapsed-placeholder ' + Ext.baseCSSPrefix + 'region-collapsed-' + comp.collapseDirection + '-placeholder ' + comp.collapsedCls,
124721                     listeners: comp.floatable ? {
124722                         click: {
124723                             fn: function(e) {
124724                                 me.floatCollapsedPanel(e, comp);
124725                             },
124726                             element: 'el'
124727                         }
124728                     } : null
124729                 };
124730                 // Hack for IE6/7/IEQuirks's inability to display an inline-block
124731                 if ((Ext.isIE6 || Ext.isIE7 || (Ext.isIEQuirks)) && !horiz) {
124732                     placeholder.width = 25;
124733                 }
124734                 if (!comp.hideCollapseTool) {
124735                     placeholder[horiz ? 'tools' : 'items'] = [{
124736                         xtype: 'tool',
124737                         client: comp,
124738                         type: 'expand-' + oppositeDirection,
124739                         handler: me.onPlaceHolderToolClick,
124740                         scope: me
124741                     }];
124742                 }
124743             }
124744             placeholder = me.owner.createComponent(placeholder);
124745             if (comp.isXType('panel')) {
124746                 comp.on({
124747                     titlechange: me.onRegionTitleChange,
124748                     iconchange: me.onRegionIconChange,
124749                     scope: me
124750                 });
124751             }
124752         }
124753
124754         // The collapsed Component holds a reference to its placeholder and vice versa
124755         comp.placeholder = placeholder;
124756         placeholder.comp = comp;
124757
124758         return placeholder;
124759     },
124760
124761     /**
124762      * @private
124763      * Update the placeholder title when panel title has been set or changed.
124764      */
124765     onRegionTitleChange: function(comp, newTitle) {
124766         comp.placeholder.setTitle(newTitle);
124767     },
124768
124769     /**
124770      * @private
124771      * Update the placeholder iconCls when panel iconCls has been set or changed.
124772      */
124773     onRegionIconChange: function(comp, newIconCls) {
124774         comp.placeholder.setIconCls(newIconCls);
124775     },
124776
124777     /**
124778      * @private
124779      * Calculates the size and positioning of the passed child item. Must be present because Panel's expand,
124780      * when configured with a flex, calls this method on its ownerCt's layout.
124781      * @param {Ext.Component} child The child Component to calculate the box for
124782      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
124783      */
124784     calculateChildBox: function(comp) {
124785         var me = this;
124786         if (me.shadowContainer.items.contains(comp)) {
124787             return me.shadowContainer.layout.calculateChildBox(comp);
124788         }
124789         else if (me.embeddedContainer && me.embeddedContainer.items.contains(comp)) {
124790             return me.embeddedContainer.layout.calculateChildBox(comp);
124791         }
124792     },
124793
124794     /**
124795      * @private
124796      * Intercepts the Panel's own collapse event and perform's substitution of the Panel
124797      * with a placeholder Header orientated in the appropriate dimension.
124798      * @param comp The Panel being collapsed.
124799      * @param direction
124800      * @param animate
124801      * @returns {Boolean} false to inhibit the Panel from performing its own collapse.
124802      */
124803     onBeforeRegionCollapse: function(comp, direction, animate) {
124804         if (comp.collapsedChangingLayout) {
124805             if (Ext.global.console && Ext.global.console.warn) {
124806                 Ext.global.console.warn(Ext.getDisplayName(arguments.callee), 'aborted because the collapsed state is in the middle of changing');
124807             }
124808             return false;
124809         }
124810         comp.collapsedChangingLayout = true;
124811         var me = this,
124812             compEl = comp.el,
124813             width,
124814             miniCollapse = comp.collapseMode == 'mini',
124815             shadowContainer = comp.shadowOwnerCt,
124816             shadowLayout = shadowContainer.layout,
124817             placeholder = comp.placeholder,
124818             sl = me.owner.suspendLayout,
124819             scsl = shadowContainer.suspendLayout,
124820             isNorthOrWest = (comp.region == 'north' || comp.region == 'west'); // Flag to keep the placeholder non-adjacent to any Splitter
124821
124822         // Do not trigger a layout during transition to collapsed Component
124823         me.owner.suspendLayout = true;
124824         shadowContainer.suspendLayout = true;
124825
124826         // Prevent upward notifications from downstream layouts
124827         shadowLayout.layoutBusy = true;
124828         if (shadowContainer.componentLayout) {
124829             shadowContainer.componentLayout.layoutBusy = true;
124830         }
124831         me.shadowContainer.layout.layoutBusy = true;
124832         me.layoutBusy = true;
124833         me.owner.componentLayout.layoutBusy = true;
124834
124835         // Provide a replacement Container with an expand tool
124836         if (!placeholder) {
124837             placeholder = me.getPlaceholder(comp);
124838         }
124839
124840         // placeholder already in place; show it.
124841         if (placeholder.shadowOwnerCt === shadowContainer) {
124842             placeholder.show();
124843         }
124844         // Insert the collapsed placeholder Component into the appropriate Box layout shadow Container
124845         // It must go next to its client Component, but non-adjacent to the splitter so splitter can find its collapse client.
124846         // Inject an ownerCt value pointing to the owner, border layout Container as the user will expect.
124847         else {
124848             shadowContainer.insert(shadowContainer.items.indexOf(comp) + (isNorthOrWest ? 0 : 1), placeholder);
124849             placeholder.shadowOwnerCt = shadowContainer;
124850             placeholder.ownerCt = me.owner;
124851         }
124852
124853         // Flag the collapsing Component as hidden and show the placeholder.
124854         // This causes the shadow Box layout's calculateChildBoxes to calculate the correct new arrangement.
124855         // We hide or slideOut the Component's element
124856         comp.hidden = true;
124857
124858         if (!placeholder.rendered) {
124859             shadowLayout.renderItem(placeholder, shadowLayout.innerCt);
124860
124861             // The inserted placeholder does not have the proper size, so copy the width
124862             // for N/S or the height for E/W from the component. This fixes EXTJSIV-1562
124863             // without recursive layouts. This is only an issue initially. After this time,
124864             // placeholder will have the correct width/height set by the layout (which has
124865             // already happened when we get here initially).
124866             if (comp.region == 'north' || comp.region == 'south') {
124867                 placeholder.setCalculatedSize(comp.getWidth());
124868             } else {
124869                 placeholder.setCalculatedSize(undefined, comp.getHeight());
124870             }
124871         }
124872
124873         // Jobs to be done after the collapse has been done
124874         function afterCollapse() {
124875             // Reinstate automatic laying out.
124876             me.owner.suspendLayout = sl;
124877             shadowContainer.suspendLayout = scsl;
124878             delete shadowLayout.layoutBusy;
124879             if (shadowContainer.componentLayout) {
124880                 delete shadowContainer.componentLayout.layoutBusy;
124881             }
124882             delete me.shadowContainer.layout.layoutBusy;
124883             delete me.layoutBusy;
124884             delete me.owner.componentLayout.layoutBusy;
124885             delete comp.collapsedChangingLayout;
124886
124887             // Fire the collapse event: The Panel has in fact been collapsed, but by substitution of an alternative Component
124888             comp.collapsed = true;
124889             comp.fireEvent('collapse', comp);
124890         }
124891
124892         /*
124893          * Set everything to the new positions. Note that we
124894          * only want to animate the collapse if it wasn't configured
124895          * initially with collapsed: true
124896          */
124897         if (comp.animCollapse && me.initialCollapsedComplete) {
124898             shadowLayout.layout();
124899             compEl.dom.style.zIndex = 100;
124900
124901             // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
124902             if (!miniCollapse) {
124903                 placeholder.el.hide();
124904             }
124905             compEl.slideOut(me.slideDirection[comp.region], {
124906                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
124907                 listeners: {
124908                     afteranimate: function() {
124909                         compEl.show().setLeftTop(-10000, -10000);
124910                         compEl.dom.style.zIndex = '';
124911
124912                         // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
124913                        if (!miniCollapse) {
124914                             placeholder.el.slideIn(me.slideDirection[comp.region], {
124915                                 easing: 'linear',
124916                                 duration: 100
124917                             });
124918                         }
124919                         afterCollapse();
124920                     }
124921                 }
124922             });
124923         } else {
124924             compEl.setLeftTop(-10000, -10000);
124925             shadowLayout.layout();
124926             afterCollapse();
124927         }
124928
124929         return false;
124930     },
124931
124932     // Hijack the expand operation to remove the placeholder and slide the region back in.
124933     onBeforeRegionExpand: function(comp, animate) {
124934         // We don't check for comp.collapsedChangingLayout here because onPlaceHolderToolClick does it
124935         this.onPlaceHolderToolClick(null, null, null, {client: comp, shouldFireBeforeexpand: false});
124936         return false;
124937     },
124938
124939     // Called when the collapsed placeholder is clicked to reinstate a "collapsed" (in reality hidden) Panel.
124940     onPlaceHolderToolClick: function(e, target, owner, tool) {
124941         var me = this,
124942             comp = tool.client,
124943
124944             // Hide the placeholder unless it was the Component's preexisting splitter
124945             hidePlaceholder = (comp.collapseMode != 'mini') || !comp.split,
124946             compEl = comp.el,
124947             toCompBox,
124948             placeholder = comp.placeholder,
124949             placeholderEl = placeholder.el,
124950             shadowContainer = comp.shadowOwnerCt,
124951             shadowLayout = shadowContainer.layout,
124952             curSize,
124953             sl = me.owner.suspendLayout,
124954             scsl = shadowContainer.suspendLayout,
124955             isFloating;
124956
124957         if (comp.collapsedChangingLayout) {
124958             if (Ext.global.console && Ext.global.console.warn) {
124959                 Ext.global.console.warn(Ext.getDisplayName(arguments.callee), 'aborted because the collapsed state is in the middle of changing');
124960             }
124961             return false;
124962         }
124963         if (tool.shouldFireBeforeexpand !== false && comp.fireEvent('beforeexpand', comp, true) === false) {
124964             return false;
124965         }
124966         comp.collapsedChangingLayout = true;
124967         // If the slide in is still going, stop it.
124968         // This will either leave the Component in its fully floated state (which is processed below)
124969         // or in its collapsed state. Either way, we expand it..
124970         if (comp.getActiveAnimation()) {
124971             comp.stopAnimation();
124972         }
124973
124974         // If the Component is fully floated when they click the placeholder Tool,
124975         // it will be primed with a slide out animation object... so delete that
124976         // and remove the mouseout listeners
124977         if (comp.slideOutAnim) {
124978             // Remove mouse leave monitors
124979             compEl.un(comp.panelMouseMon);
124980             placeholderEl.un(comp.placeholderMouseMon);
124981
124982             delete comp.slideOutAnim;
124983             delete comp.panelMouseMon;
124984             delete comp.placeholderMouseMon;
124985
124986             // If the Panel was floated and primed with a slideOut animation, we don't want to animate its layout operation.
124987             isFloating = true;
124988         }
124989
124990         // Do not trigger a layout during transition to expanded Component
124991         me.owner.suspendLayout = true;
124992         shadowContainer.suspendLayout = true;
124993
124994         // Prevent upward notifications from downstream layouts
124995         shadowLayout.layoutBusy = true;
124996         if (shadowContainer.componentLayout) {
124997             shadowContainer.componentLayout.layoutBusy = true;
124998         }
124999         me.shadowContainer.layout.layoutBusy = true;
125000         me.layoutBusy = true;
125001         me.owner.componentLayout.layoutBusy = true;
125002
125003         // Unset the hidden and collapsed flags set in onBeforeRegionCollapse. The shadowLayout will now take it into account
125004         // Find where the shadow Box layout plans to put the expanding Component.
125005         comp.hidden = false;
125006         comp.collapsed = false;
125007         if (hidePlaceholder) {
125008             placeholder.hidden = true;
125009         }
125010         toCompBox = shadowLayout.calculateChildBox(comp);
125011
125012         // Show the collapse tool in case it was hidden by the slide-in
125013         if (comp.collapseTool) {
125014             comp.collapseTool.show();
125015         }
125016
125017         // If we're going to animate, we need to hide the component before moving it back into position
125018         if (comp.animCollapse && !isFloating) {
125019             compEl.setStyle('visibility', 'hidden');
125020         }
125021         compEl.setLeftTop(toCompBox.left, toCompBox.top);
125022
125023         // Equalize the size of the expanding Component prior to animation
125024         // in case the layout area has changed size during the time it was collapsed.
125025         curSize = comp.getSize();
125026         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
125027             me.setItemSize(comp, toCompBox.width, toCompBox.height);
125028         }
125029
125030         // Jobs to be done after the expand has been done
125031         function afterExpand() {
125032             // Reinstate automatic laying out.
125033             me.owner.suspendLayout = sl;
125034             shadowContainer.suspendLayout = scsl;
125035             delete shadowLayout.layoutBusy;
125036             if (shadowContainer.componentLayout) {
125037                 delete shadowContainer.componentLayout.layoutBusy;
125038             }
125039             delete me.shadowContainer.layout.layoutBusy;
125040             delete me.layoutBusy;
125041             delete me.owner.componentLayout.layoutBusy;
125042             delete comp.collapsedChangingLayout;
125043
125044             // In case it was floated out and they clicked the re-expand tool
125045             comp.removeCls(Ext.baseCSSPrefix + 'border-region-slide-in');
125046
125047             // Fire the expand event: The Panel has in fact been expanded, but by removal of an alternative Component
125048             comp.fireEvent('expand', comp);
125049         }
125050
125051         // Hide the placeholder
125052         if (hidePlaceholder) {
125053             placeholder.el.hide();
125054         }
125055
125056         // Slide the expanding Component to its new position.
125057         // When that is done, layout the layout.
125058         if (comp.animCollapse && !isFloating) {
125059             compEl.dom.style.zIndex = 100;
125060             compEl.slideIn(me.slideDirection[comp.region], {
125061                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
125062                 listeners: {
125063                     afteranimate: function() {
125064                         compEl.dom.style.zIndex = '';
125065                         comp.hidden = false;
125066                         shadowLayout.onLayout();
125067                         afterExpand();
125068                     }
125069                 }
125070             });
125071         } else {
125072             shadowLayout.onLayout();
125073             afterExpand();
125074         }
125075     },
125076
125077     floatCollapsedPanel: function(e, comp) {
125078
125079         if (comp.floatable === false) {
125080             return;
125081         }
125082
125083         var me = this,
125084             compEl = comp.el,
125085             placeholder = comp.placeholder,
125086             placeholderEl = placeholder.el,
125087             shadowContainer = comp.shadowOwnerCt,
125088             shadowLayout = shadowContainer.layout,
125089             placeholderBox = shadowLayout.getChildBox(placeholder),
125090             scsl = shadowContainer.suspendLayout,
125091             curSize, toCompBox, compAnim;
125092
125093         // Ignore clicks on tools.
125094         if (e.getTarget('.' + Ext.baseCSSPrefix + 'tool')) {
125095             return;
125096         }
125097
125098         // It's *being* animated, ignore the click.
125099         // Possible future enhancement: Stop and *reverse* the current active Fx.
125100         if (compEl.getActiveAnimation()) {
125101             return;
125102         }
125103
125104         // If the Component is already fully floated when they click the placeholder,
125105         // it will be primed with a slide out animation object... so slide it out.
125106         if (comp.slideOutAnim) {
125107             me.slideOutFloatedComponent(comp);
125108             return;
125109         }
125110
125111         // Function to be called when the mouse leaves the floated Panel
125112         // Slide out when the mouse leaves the region bounded by the slid Component and its placeholder.
125113         function onMouseLeaveFloated(e) {
125114             var slideRegion = compEl.getRegion().union(placeholderEl.getRegion()).adjust(1, -1, -1, 1);
125115
125116             // If mouse is not within slide Region, slide it out
125117             if (!slideRegion.contains(e.getPoint())) {
125118                 me.slideOutFloatedComponent(comp);
125119             }
125120         }
125121
125122         // Monitor for mouseouting of the placeholder. Hide it if they exit for half a second or more
125123         comp.placeholderMouseMon = placeholderEl.monitorMouseLeave(500, onMouseLeaveFloated);
125124
125125         // Do not trigger a layout during slide out of the Component
125126         shadowContainer.suspendLayout = true;
125127
125128         // Prevent upward notifications from downstream layouts
125129         me.layoutBusy = true;
125130         me.owner.componentLayout.layoutBusy = true;
125131
125132         // The collapse tool is hidden while slid.
125133         // It is re-shown on expand.
125134         if (comp.collapseTool) {
125135             comp.collapseTool.hide();
125136         }
125137
125138         // Set flags so that the layout will calculate the boxes for what we want
125139         comp.hidden = false;
125140         comp.collapsed = false;
125141         placeholder.hidden = true;
125142
125143         // Recalculate new arrangement of the Component being floated.
125144         toCompBox = shadowLayout.calculateChildBox(comp);
125145         placeholder.hidden = false;
125146
125147         // Component to appear just after the placeholder, whatever "after" means in the context of the shadow Box layout.
125148         if (comp.region == 'north' || comp.region == 'west') {
125149             toCompBox[shadowLayout.parallelBefore] += placeholderBox[shadowLayout.parallelPrefix] - 1;
125150         } else {
125151             toCompBox[shadowLayout.parallelBefore] -= (placeholderBox[shadowLayout.parallelPrefix] - 1);
125152         }
125153         compEl.setStyle('visibility', 'hidden');
125154         compEl.setLeftTop(toCompBox.left, toCompBox.top);
125155
125156         // Equalize the size of the expanding Component prior to animation
125157         // in case the layout area has changed size during the time it was collapsed.
125158         curSize = comp.getSize();
125159         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
125160             me.setItemSize(comp, toCompBox.width, toCompBox.height);
125161         }
125162
125163         // This animation slides the collapsed Component's el out to just beyond its placeholder
125164         compAnim = {
125165             listeners: {
125166                 afteranimate: function() {
125167                     shadowContainer.suspendLayout = scsl;
125168                     delete me.layoutBusy;
125169                     delete me.owner.componentLayout.layoutBusy;
125170
125171                     // Prime the Component with an Anim config object to slide it back out
125172                     compAnim.listeners = {
125173                         afterAnimate: function() {
125174                             compEl.show().removeCls(Ext.baseCSSPrefix + 'border-region-slide-in').setLeftTop(-10000, -10000);
125175
125176                             // Reinstate the correct, current state after slide out animation finishes
125177                             comp.hidden = true;
125178                             comp.collapsed = true;
125179                             delete comp.slideOutAnim;
125180                             delete comp.panelMouseMon;
125181                             delete comp.placeholderMouseMon;
125182                         }
125183                     };
125184                     comp.slideOutAnim = compAnim;
125185                 }
125186             },
125187             duration: 500
125188         };
125189
125190         // Give the element the correct class which places it at a high z-index
125191         compEl.addCls(Ext.baseCSSPrefix + 'border-region-slide-in');
125192
125193         // Begin the slide in
125194         compEl.slideIn(me.slideDirection[comp.region], compAnim);
125195
125196         // Monitor for mouseouting of the slid area. Hide it if they exit for half a second or more
125197         comp.panelMouseMon = compEl.monitorMouseLeave(500, onMouseLeaveFloated);
125198
125199     },
125200
125201     slideOutFloatedComponent: function(comp) {
125202         var compEl = comp.el,
125203             slideOutAnim;
125204
125205         // Remove mouse leave monitors
125206         compEl.un(comp.panelMouseMon);
125207         comp.placeholder.el.un(comp.placeholderMouseMon);
125208
125209         // Slide the Component out
125210         compEl.slideOut(this.slideDirection[comp.region], comp.slideOutAnim);
125211
125212         delete comp.slideOutAnim;
125213         delete comp.panelMouseMon;
125214         delete comp.placeholderMouseMon;
125215     },
125216
125217     /*
125218      * @private
125219      * Ensure any collapsed placeholder Component is destroyed along with its region.
125220      * Can't do this in onDestroy because they may remove a Component and use it elsewhere.
125221      */
125222     onRegionDestroy: function(comp) {
125223         var placeholder = comp.placeholder;
125224         if (placeholder) {
125225             delete placeholder.ownerCt;
125226             placeholder.destroy();
125227         }
125228     },
125229
125230     /*
125231      * @private
125232      * Ensure any shadow Containers are destroyed.
125233      * Ensure we don't keep references to Components.
125234      */
125235     onDestroy: function() {
125236         var me = this,
125237             shadowContainer = me.shadowContainer,
125238             embeddedContainer = me.embeddedContainer;
125239
125240         if (shadowContainer) {
125241             delete shadowContainer.ownerCt;
125242             Ext.destroy(shadowContainer);
125243         }
125244
125245         if (embeddedContainer) {
125246             delete embeddedContainer.ownerCt;
125247             Ext.destroy(embeddedContainer);
125248         }
125249         delete me.regions;
125250         delete me.splitters;
125251         delete me.shadowContainer;
125252         delete me.embeddedContainer;
125253         me.callParent(arguments);
125254     }
125255 });
125256
125257 /**
125258  * This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
125259  * visible at any given time.  This layout style is most commonly used for wizards, tab implementations, etc.
125260  * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,
125261  * and should generally not need to be created directly via the new keyword.
125262  *
125263  * The CardLayout's focal method is {@link #setActiveItem}.  Since only one panel is displayed at a time,
125264  * the only way to move from one Component to the next is by calling setActiveItem, passing the next panel to display
125265  * (or its id or index).  The layout itself does not provide a user interface for handling this navigation,
125266  * so that functionality must be provided by the developer.
125267  *
125268  * To change the active card of a container, call the setActiveItem method of its layout:
125269  *
125270  *     Ext.create('Ext.panel.Panel', {
125271  *         layout: 'card',
125272  *         items: [
125273  *             { html: 'Card 1' },
125274  *             { html: 'Card 2' }
125275  *         ],
125276  *         renderTo: Ext.getBody()
125277  *     });
125278  *
125279  *     p.getLayout().setActiveItem(1);
125280  *
125281  * In the following example, a simplistic wizard setup is demonstrated.  A button bar is added
125282  * to the footer of the containing panel to provide navigation buttons.  The buttons will be handled by a
125283  * common navigation routine.  Note that other uses of a CardLayout (like a tab control) would require a
125284  * completely different implementation.  For serious implementations, a better approach would be to extend
125285  * CardLayout to provide the custom functionality needed.
125286  *
125287  *     @example
125288  *     var navigate = function(panel, direction){
125289  *         // This routine could contain business logic required to manage the navigation steps.
125290  *         // It would call setActiveItem as needed, manage navigation button state, handle any
125291  *         // branching logic that might be required, handle alternate actions like cancellation
125292  *         // or finalization, etc.  A complete wizard implementation could get pretty
125293  *         // sophisticated depending on the complexity required, and should probably be
125294  *         // done as a subclass of CardLayout in a real-world implementation.
125295  *         var layout = panel.getLayout();
125296  *         layout[direction]();
125297  *         Ext.getCmp('move-prev').setDisabled(!layout.getPrev());
125298  *         Ext.getCmp('move-next').setDisabled(!layout.getNext());
125299  *     };
125300  *
125301  *     Ext.create('Ext.panel.Panel', {
125302  *         title: 'Example Wizard',
125303  *         width: 300,
125304  *         height: 200,
125305  *         layout: 'card',
125306  *         bodyStyle: 'padding:15px',
125307  *         defaults: {
125308  *             // applied to each contained panel
125309  *             border: false
125310  *         },
125311  *         // just an example of one possible navigation scheme, using buttons
125312  *         bbar: [
125313  *             {
125314  *                 id: 'move-prev',
125315  *                 text: 'Back',
125316  *                 handler: function(btn) {
125317  *                     navigate(btn.up("panel"), "prev");
125318  *                 },
125319  *                 disabled: true
125320  *             },
125321  *             '->', // greedy spacer so that the buttons are aligned to each side
125322  *             {
125323  *                 id: 'move-next',
125324  *                 text: 'Next',
125325  *                 handler: function(btn) {
125326  *                     navigate(btn.up("panel"), "next");
125327  *                 }
125328  *             }
125329  *         ],
125330  *         // the panels (or "cards") within the layout
125331  *         items: [{
125332  *             id: 'card-0',
125333  *             html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
125334  *         },{
125335  *             id: 'card-1',
125336  *             html: '<p>Step 2 of 3</p>'
125337  *         },{
125338  *             id: 'card-2',
125339  *             html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
125340  *         }],
125341  *         renderTo: Ext.getBody()
125342  *     });
125343  */
125344 Ext.define('Ext.layout.container.Card', {
125345
125346     /* Begin Definitions */
125347
125348     alias: ['layout.card'],
125349     alternateClassName: 'Ext.layout.CardLayout',
125350
125351     extend: 'Ext.layout.container.AbstractCard',
125352
125353     /* End Definitions */
125354
125355     /**
125356      * Makes the given card active.
125357      *
125358      *     var card1 = Ext.create('Ext.panel.Panel', {itemId: 'card-1'});
125359      *     var card2 = Ext.create('Ext.panel.Panel', {itemId: 'card-2'});
125360      *     var panel = Ext.create('Ext.panel.Panel', {
125361      *         layout: 'card',
125362      *         activeItem: 0,
125363      *         items: [card1, card2]
125364      *     });
125365      *     // These are all equivalent
125366      *     panel.getLayout().setActiveItem(card2);
125367      *     panel.getLayout().setActiveItem('card-2');
125368      *     panel.getLayout().setActiveItem(1);
125369      *
125370      * @param {Ext.Component/Number/String} newCard  The component, component {@link Ext.Component#id id},
125371      * {@link Ext.Component#itemId itemId}, or index of component.
125372      * @return {Ext.Component} the activated component or false when nothing activated.
125373      * False is returned also when trying to activate an already active card.
125374      */
125375     setActiveItem: function(newCard) {
125376         var me = this,
125377             owner = me.owner,
125378             oldCard = me.activeItem,
125379             newIndex;
125380
125381         newCard = me.parseActiveItem(newCard);
125382         newIndex = owner.items.indexOf(newCard);
125383
125384         // If the card is not a child of the owner, then add it
125385         if (newIndex == -1) {
125386             newIndex = owner.items.items.length;
125387             owner.add(newCard);
125388         }
125389
125390         // Is this a valid, different card?
125391         if (newCard && oldCard != newCard) {
125392             // If the card has not been rendered yet, now is the time to do so.
125393             if (!newCard.rendered) {
125394                 me.renderItem(newCard, me.getRenderTarget(), owner.items.length);
125395                 me.configureItem(newCard, 0);
125396             }
125397
125398             me.activeItem = newCard;
125399
125400             // Fire the beforeactivate and beforedeactivate events on the cards
125401             if (newCard.fireEvent('beforeactivate', newCard, oldCard) === false) {
125402                 return false;
125403             }
125404             if (oldCard && oldCard.fireEvent('beforedeactivate', oldCard, newCard) === false) {
125405                 return false;
125406             }
125407
125408             // If the card hasnt been sized yet, do it now
125409             if (me.sizeAllCards) {
125410                 // onLayout calls setItemBox
125411                 me.onLayout();
125412             }
125413             else {
125414                 me.setItemBox(newCard, me.getTargetBox());
125415             }
125416
125417             me.owner.suspendLayout = true;
125418
125419             if (oldCard) {
125420                 if (me.hideInactive) {
125421                     oldCard.hide();
125422                 }
125423                 oldCard.fireEvent('deactivate', oldCard, newCard);
125424             }
125425
125426             // Make sure the new card is shown
125427             me.owner.suspendLayout = false;
125428             if (newCard.hidden) {
125429                 newCard.show();
125430             } else {
125431                 me.onLayout();
125432             }
125433
125434             newCard.fireEvent('activate', newCard, oldCard);
125435
125436             return newCard;
125437         }
125438         return false;
125439     },
125440
125441     configureItem: function(item) {
125442         // Card layout only controls dimensions which IT has controlled.
125443         // That calculation has to be determined at run time by examining the ownerCt's isFixedWidth()/isFixedHeight() methods
125444         item.layoutManagedHeight = 0;
125445         item.layoutManagedWidth = 0;
125446
125447         this.callParent(arguments);
125448     }});
125449 /**
125450  * This is the layout style of choice for creating structural layouts in a multi-column format where the width of each
125451  * column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content. This
125452  * class is intended to be extended or created via the layout:'column' {@link Ext.container.Container#layout} config,
125453  * and should generally not need to be created directly via the new keyword.
125454  *
125455  * ColumnLayout does not have any direct config options (other than inherited ones), but it does support a specific
125456  * config property of `columnWidth` that can be included in the config of any panel added to it. The layout will use
125457  * the columnWidth (if present) or width of each panel during layout to determine how to size each panel. If width or
125458  * columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).
125459  *
125460  * The width property is always evaluated as pixels, and must be a number greater than or equal to 1. The columnWidth
125461  * property is always evaluated as a percentage, and must be a decimal value greater than 0 and less than 1 (e.g., .25).
125462  *
125463  * The basic rules for specifying column widths are pretty simple. The logic makes two passes through the set of
125464  * contained panels. During the first layout pass, all panels that either have a fixed width or none specified (auto)
125465  * are skipped, but their widths are subtracted from the overall container width.
125466  *
125467  * During the second pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages
125468  * based on the total **remaining** container width. In other words, percentage width panels are designed to fill
125469  * the space left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any
125470  * number of columns with different percentages, the columnWidths must always add up to 1 (or 100%) when added
125471  * together, otherwise your layout may not render as expected.
125472  *
125473  *     @example
125474  *     // All columns are percentages -- they must add up to 1
125475  *     Ext.create('Ext.panel.Panel', {
125476  *         title: 'Column Layout - Percentage Only',
125477  *         width: 350,
125478  *         height: 250,
125479  *         layout:'column',
125480  *         items: [{
125481  *             title: 'Column 1',
125482  *             columnWidth: .25
125483  *         },{
125484  *             title: 'Column 2',
125485  *             columnWidth: .55
125486  *         },{
125487  *             title: 'Column 3',
125488  *             columnWidth: .20
125489  *         }],
125490  *         renderTo: Ext.getBody()
125491  *     });
125492  *
125493  *     // Mix of width and columnWidth -- all columnWidth values must add up
125494  *     // to 1. The first column will take up exactly 120px, and the last two
125495  *     // columns will fill the remaining container width.
125496  *
125497  *     Ext.create('Ext.Panel', {
125498  *         title: 'Column Layout - Mixed',
125499  *         width: 350,
125500  *         height: 250,
125501  *         layout:'column',
125502  *         items: [{
125503  *             title: 'Column 1',
125504  *             width: 120
125505  *         },{
125506  *             title: 'Column 2',
125507  *             columnWidth: .7
125508  *         },{
125509  *             title: 'Column 3',
125510  *             columnWidth: .3
125511  *         }],
125512  *         renderTo: Ext.getBody()
125513  *     });
125514  */
125515 Ext.define('Ext.layout.container.Column', {
125516
125517     extend: 'Ext.layout.container.Auto',
125518     alias: ['layout.column'],
125519     alternateClassName: 'Ext.layout.ColumnLayout',
125520
125521     type: 'column',
125522
125523     itemCls: Ext.baseCSSPrefix + 'column',
125524
125525     targetCls: Ext.baseCSSPrefix + 'column-layout-ct',
125526
125527     scrollOffset: 0,
125528
125529     bindToOwnerCtComponent: false,
125530
125531     getRenderTarget : function() {
125532         if (!this.innerCt) {
125533
125534             // the innerCt prevents wrapping and shuffling while
125535             // the container is resizing
125536             this.innerCt = this.getTarget().createChild({
125537                 cls: Ext.baseCSSPrefix + 'column-inner'
125538             });
125539
125540             // Column layout uses natural HTML flow to arrange the child items.
125541             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
125542             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
125543             this.clearEl = this.innerCt.createChild({
125544                 cls: Ext.baseCSSPrefix + 'clear',
125545                 role: 'presentation'
125546             });
125547         }
125548         return this.innerCt;
125549     },
125550
125551     // private
125552     onLayout : function() {
125553         var me = this,
125554             target = me.getTarget(),
125555             items = me.getLayoutItems(),
125556             len = items.length,
125557             item,
125558             i,
125559             parallelMargins = [],
125560             itemParallelMargins,
125561             size,
125562             availableWidth,
125563             columnWidth;
125564
125565         size = me.getLayoutTargetSize();
125566         if (size.width < len * 10) { // Don't lay out in impossibly small target (probably display:none, or initial, unsized Container)
125567             return;
125568         }
125569
125570         // On the first pass, for all except IE6-7, we lay out the items with no scrollbars visible using style overflow: hidden.
125571         // If, after the layout, it is detected that there is vertical overflow,
125572         // we will recurse back through here. Do not adjust overflow style at that time.
125573         if (me.adjustmentPass) {
125574             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
125575                 size.width = me.adjustedWidth;
125576             }
125577         } else {
125578             i = target.getStyle('overflow');
125579             if (i && i != 'hidden') {
125580                 me.autoScroll = true;
125581                 if (!(Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks)) {
125582                     target.setStyle('overflow', 'hidden');
125583                     size = me.getLayoutTargetSize();
125584                 }
125585             }
125586         }
125587
125588         availableWidth = size.width - me.scrollOffset;
125589         me.innerCt.setWidth(availableWidth);
125590
125591         // some columns can be percentages while others are fixed
125592         // so we need to make 2 passes
125593         for (i = 0; i < len; i++) {
125594             item = items[i];
125595             itemParallelMargins = parallelMargins[i] = item.getEl().getMargin('lr');
125596             if (!item.columnWidth) {
125597                 availableWidth -= (item.getWidth() + itemParallelMargins);
125598             }
125599         }
125600
125601         availableWidth = availableWidth < 0 ? 0 : availableWidth;
125602         for (i = 0; i < len; i++) {
125603             item = items[i];
125604             if (item.columnWidth) {
125605                 columnWidth = Math.floor(item.columnWidth * availableWidth) - parallelMargins[i];
125606                 me.setItemSize(item, columnWidth, item.height);
125607             } else {
125608                 me.layoutItem(item);
125609             }
125610         }
125611
125612         // After the first pass on an autoScroll layout, restore the overflow settings if it had been changed (only changed for non-IE6)
125613         if (!me.adjustmentPass && me.autoScroll) {
125614
125615             // If there's a vertical overflow, relay with scrollbars
125616             target.setStyle('overflow', 'auto');
125617             me.adjustmentPass = (target.dom.scrollHeight > size.height);
125618             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
125619                 me.adjustedWidth = size.width - Ext.getScrollBarWidth();
125620             } else {
125621                 target.setStyle('overflow', 'auto');
125622             }
125623
125624             // If the layout caused height overflow, recurse back and recalculate (with overflow setting restored on non-IE6)
125625             if (me.adjustmentPass) {
125626                 me.onLayout();
125627             }
125628         }
125629         delete me.adjustmentPass;
125630     },
125631
125632     configureItem: function(item) {
125633         this.callParent(arguments);
125634
125635         if (item.columnWidth) {
125636             item.layoutManagedWidth = 1;
125637         }
125638     }
125639 });
125640 /**
125641  * This layout allows you to easily render content into an HTML table. The total number of columns can be specified, and
125642  * rowspan and colspan can be used to create complex layouts within the table. This class is intended to be extended or
125643  * created via the `layout: {type: 'table'}` {@link Ext.container.Container#layout} config, and should generally not
125644  * need to be created directly via the new keyword.
125645  *
125646  * Note that when creating a layout via config, the layout-specific config properties must be passed in via the {@link
125647  * Ext.container.Container#layout} object which will then be applied internally to the layout. In the case of
125648  * TableLayout, the only valid layout config properties are {@link #columns} and {@link #tableAttrs}. However, the items
125649  * added to a TableLayout can supply the following table-specific config properties:
125650  *
125651  *   - **rowspan** Applied to the table cell containing the item.
125652  *   - **colspan** Applied to the table cell containing the item.
125653  *   - **cellId** An id applied to the table cell containing the item.
125654  *   - **cellCls** A CSS class name added to the table cell containing the item.
125655  *
125656  * The basic concept of building up a TableLayout is conceptually very similar to building up a standard HTML table. You
125657  * simply add each panel (or "cell") that you want to include along with any span attributes specified as the special
125658  * config properties of rowspan and colspan which work exactly like their HTML counterparts. Rather than explicitly
125659  * creating and nesting rows and columns as you would in HTML, you simply specify the total column count in the
125660  * layoutConfig and start adding panels in their natural order from left to right, top to bottom. The layout will
125661  * automatically figure out, based on the column count, rowspans and colspans, how to position each panel within the
125662  * table. Just like with HTML tables, your rowspans and colspans must add up correctly in your overall layout or you'll
125663  * end up with missing and/or extra cells! Example usage:
125664  *
125665  *     @example
125666  *     Ext.create('Ext.panel.Panel', {
125667  *         title: 'Table Layout',
125668  *         width: 300,
125669  *         height: 150,
125670  *         layout: {
125671  *             type: 'table',
125672  *             // The total column count must be specified here
125673  *             columns: 3
125674  *         },
125675  *         defaults: {
125676  *             // applied to each contained panel
125677  *             bodyStyle: 'padding:20px'
125678  *         },
125679  *         items: [{
125680  *             html: 'Cell A content',
125681  *             rowspan: 2
125682  *         },{
125683  *             html: 'Cell B content',
125684  *             colspan: 2
125685  *         },{
125686  *             html: 'Cell C content',
125687  *             cellCls: 'highlight'
125688  *         },{
125689  *             html: 'Cell D content'
125690  *         }],
125691  *         renderTo: Ext.getBody()
125692  *     });
125693  */
125694 Ext.define('Ext.layout.container.Table', {
125695
125696     /* Begin Definitions */
125697
125698     alias: ['layout.table'],
125699     extend: 'Ext.layout.container.Auto',
125700     alternateClassName: 'Ext.layout.TableLayout',
125701
125702     /* End Definitions */
125703
125704     /**
125705      * @cfg {Number} columns
125706      * The total number of columns to create in the table for this layout. If not specified, all Components added to
125707      * this layout will be rendered into a single row using one column per Component.
125708      */
125709
125710     // private
125711     monitorResize:false,
125712
125713     type: 'table',
125714
125715     // Table layout is a self-sizing layout. When an item of for example, a dock layout, the Panel must expand to accommodate
125716     // a table layout. See in particular AbstractDock::onLayout for use of this flag.
125717     autoSize: true,
125718
125719     clearEl: true, // Base class will not create it if already truthy. Not needed in tables.
125720
125721     targetCls: Ext.baseCSSPrefix + 'table-layout-ct',
125722     tableCls: Ext.baseCSSPrefix + 'table-layout',
125723     cellCls: Ext.baseCSSPrefix + 'table-layout-cell',
125724
125725     /**
125726      * @cfg {Object} tableAttrs
125727      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
125728      * create the layout's `<table>` element. Example:
125729      *
125730      *     {
125731      *         xtype: 'panel',
125732      *         layout: {
125733      *             type: 'table',
125734      *             columns: 3,
125735      *             tableAttrs: {
125736      *                 style: {
125737      *                     width: '100%'
125738      *                 }
125739      *             }
125740      *         }
125741      *     }
125742      */
125743     tableAttrs:null,
125744
125745     /**
125746      * @cfg {Object} trAttrs
125747      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
125748      * create the layout's <tr> elements.
125749      */
125750
125751     /**
125752      * @cfg {Object} tdAttrs
125753      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
125754      * create the layout's <td> elements.
125755      */
125756
125757     /**
125758      * @private
125759      * Iterates over all passed items, ensuring they are rendered in a cell in the proper
125760      * location in the table structure.
125761      */
125762     renderItems: function(items) {
125763         var tbody = this.getTable().tBodies[0],
125764             rows = tbody.rows,
125765             i = 0,
125766             len = items.length,
125767             cells, curCell, rowIdx, cellIdx, item, trEl, tdEl, itemCt;
125768
125769         // Calculate the correct cell structure for the current items
125770         cells = this.calculateCells(items);
125771
125772         // Loop over each cell and compare to the current cells in the table, inserting/
125773         // removing/moving cells as needed, and making sure each item is rendered into
125774         // the correct cell.
125775         for (; i < len; i++) {
125776             curCell = cells[i];
125777             rowIdx = curCell.rowIdx;
125778             cellIdx = curCell.cellIdx;
125779             item = items[i];
125780
125781             // If no row present, create and insert one
125782             trEl = rows[rowIdx];
125783             if (!trEl) {
125784                 trEl = tbody.insertRow(rowIdx);
125785                 if (this.trAttrs) {
125786                     trEl.set(this.trAttrs);
125787                 }
125788             }
125789
125790             // If no cell present, create and insert one
125791             itemCt = tdEl = Ext.get(trEl.cells[cellIdx] || trEl.insertCell(cellIdx));
125792             if (this.needsDivWrap()) { //create wrapper div if needed - see docs below
125793                 itemCt = tdEl.first() || tdEl.createChild({tag: 'div'});
125794                 itemCt.setWidth(null);
125795             }
125796
125797             // Render or move the component into the cell
125798             if (!item.rendered) {
125799                 this.renderItem(item, itemCt, 0);
125800             }
125801             else if (!this.isValidParent(item, itemCt, 0)) {
125802                 this.moveItem(item, itemCt, 0);
125803             }
125804
125805             // Set the cell properties
125806             if (this.tdAttrs) {
125807                 tdEl.set(this.tdAttrs);
125808             }
125809             tdEl.set({
125810                 colSpan: item.colspan || 1,
125811                 rowSpan: item.rowspan || 1,
125812                 id: item.cellId || '',
125813                 cls: this.cellCls + ' ' + (item.cellCls || '')
125814             });
125815
125816             // If at the end of a row, remove any extra cells
125817             if (!cells[i + 1] || cells[i + 1].rowIdx !== rowIdx) {
125818                 cellIdx++;
125819                 while (trEl.cells[cellIdx]) {
125820                     trEl.deleteCell(cellIdx);
125821                 }
125822             }
125823         }
125824
125825         // Delete any extra rows
125826         rowIdx++;
125827         while (tbody.rows[rowIdx]) {
125828             tbody.deleteRow(rowIdx);
125829         }
125830     },
125831
125832     afterLayout: function() {
125833         this.callParent();
125834
125835         if (this.needsDivWrap()) {
125836             // set wrapper div width to match layed out item - see docs below
125837             Ext.Array.forEach(this.getLayoutItems(), function(item) {
125838                 Ext.fly(item.el.dom.parentNode).setWidth(item.getWidth());
125839             });
125840         }
125841     },
125842
125843     /**
125844      * @private
125845      * Determine the row and cell indexes for each component, taking into consideration
125846      * the number of columns and each item's configured colspan/rowspan values.
125847      * @param {Array} items The layout components
125848      * @return {Object[]} List of row and cell indexes for each of the components
125849      */
125850     calculateCells: function(items) {
125851         var cells = [],
125852             rowIdx = 0,
125853             colIdx = 0,
125854             cellIdx = 0,
125855             totalCols = this.columns || Infinity,
125856             rowspans = [], //rolling list of active rowspans for each column
125857             i = 0, j,
125858             len = items.length,
125859             item;
125860
125861         for (; i < len; i++) {
125862             item = items[i];
125863
125864             // Find the first available row/col slot not taken up by a spanning cell
125865             while (colIdx >= totalCols || rowspans[colIdx] > 0) {
125866                 if (colIdx >= totalCols) {
125867                     // move down to next row
125868                     colIdx = 0;
125869                     cellIdx = 0;
125870                     rowIdx++;
125871
125872                     // decrement all rowspans
125873                     for (j = 0; j < totalCols; j++) {
125874                         if (rowspans[j] > 0) {
125875                             rowspans[j]--;
125876                         }
125877                     }
125878                 } else {
125879                     colIdx++;
125880                 }
125881             }
125882
125883             // Add the cell info to the list
125884             cells.push({
125885                 rowIdx: rowIdx,
125886                 cellIdx: cellIdx
125887             });
125888
125889             // Increment
125890             for (j = item.colspan || 1; j; --j) {
125891                 rowspans[colIdx] = item.rowspan || 1;
125892                 ++colIdx;
125893             }
125894             ++cellIdx;
125895         }
125896
125897         return cells;
125898     },
125899
125900     /**
125901      * @private
125902      * Return the layout's table element, creating it if necessary.
125903      */
125904     getTable: function() {
125905         var table = this.table;
125906         if (!table) {
125907             table = this.table = this.getTarget().createChild(
125908                 Ext.apply({
125909                     tag: 'table',
125910                     role: 'presentation',
125911                     cls: this.tableCls,
125912                     cellspacing: 0, //TODO should this be specified or should CSS handle it?
125913                     cn: {tag: 'tbody'}
125914                 }, this.tableAttrs),
125915                 null, true
125916             );
125917         }
125918         return table;
125919     },
125920
125921     /**
125922      * @private
125923      * Opera 10.5 has a bug where if a table cell's child has box-sizing:border-box and padding, it
125924      * will include that padding in the size of the cell, making it always larger than the
125925      * shrink-wrapped size of its contents. To get around this we have to wrap the contents in a div
125926      * and then set that div's width to match the item rendered within it afterLayout. This method
125927      * determines whether we need the wrapper div; it currently does a straight UA sniff as this bug
125928      * seems isolated to just Opera 10.5, but feature detection could be added here if needed.
125929      */
125930     needsDivWrap: function() {
125931         return Ext.isOpera10_5;
125932     }
125933 });
125934 /**
125935  * A base class for all menu items that require menu-related functionality such as click handling,
125936  * sub-menus, icons, etc.
125937  *
125938  *     @example
125939  *     Ext.create('Ext.menu.Menu', {
125940  *         width: 100,
125941  *         height: 100,
125942  *         floating: false,  // usually you want this set to True (default)
125943  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125944  *         items: [{
125945  *             text: 'icon item',
125946  *             iconCls: 'add16'
125947  *         },{
125948  *             text: 'text item'
125949  *         },{
125950  *             text: 'plain item',
125951  *             plain: true
125952  *         }]
125953  *     });
125954  */
125955 Ext.define('Ext.menu.Item', {
125956     extend: 'Ext.Component',
125957     alias: 'widget.menuitem',
125958     alternateClassName: 'Ext.menu.TextItem',
125959
125960     /**
125961      * @property {Boolean} activated
125962      * Whether or not this item is currently activated
125963      */
125964
125965     /**
125966      * @property {Ext.menu.Menu} parentMenu
125967      * The parent Menu of this item.
125968      */
125969
125970     /**
125971      * @cfg {String} activeCls
125972      * The CSS class added to the menu item when the item is activated (focused/mouseover).
125973      * Defaults to `Ext.baseCSSPrefix + 'menu-item-active'`.
125974      */
125975     activeCls: Ext.baseCSSPrefix + 'menu-item-active',
125976
125977     /**
125978      * @cfg {String} ariaRole @hide
125979      */
125980     ariaRole: 'menuitem',
125981
125982     /**
125983      * @cfg {Boolean} canActivate
125984      * Whether or not this menu item can be activated when focused/mouseovered. Defaults to `true`.
125985      */
125986     canActivate: true,
125987
125988     /**
125989      * @cfg {Number} clickHideDelay
125990      * The delay in milliseconds to wait before hiding the menu after clicking the menu item.
125991      * This only has an effect when `hideOnClick: true`. Defaults to `1`.
125992      */
125993     clickHideDelay: 1,
125994
125995     /**
125996      * @cfg {Boolean} destroyMenu
125997      * Whether or not to destroy any associated sub-menu when this item is destroyed. Defaults to `true`.
125998      */
125999     destroyMenu: true,
126000
126001     /**
126002      * @cfg {String} disabledCls
126003      * The CSS class added to the menu item when the item is disabled.
126004      * Defaults to `Ext.baseCSSPrefix + 'menu-item-disabled'`.
126005      */
126006     disabledCls: Ext.baseCSSPrefix + 'menu-item-disabled',
126007
126008     /**
126009      * @cfg {String} href
126010      * The href attribute to use for the underlying anchor link. Defaults to `#`.
126011      * @markdown
126012      */
126013
126014      /**
126015       * @cfg {String} hrefTarget
126016       * The target attribute to use for the underlying anchor link. Defaults to `undefined`.
126017       * @markdown
126018       */
126019
126020     /**
126021      * @cfg {Boolean} hideOnClick
126022      * Whether to not to hide the owning menu when this item is clicked. Defaults to `true`.
126023      * @markdown
126024      */
126025     hideOnClick: true,
126026
126027     /**
126028      * @cfg {String} icon
126029      * The path to an icon to display in this item. Defaults to `Ext.BLANK_IMAGE_URL`.
126030      * @markdown
126031      */
126032
126033     /**
126034      * @cfg {String} iconCls
126035      * A CSS class that specifies a `background-image` to use as the icon for this item. Defaults to `undefined`.
126036      * @markdown
126037      */
126038
126039     isMenuItem: true,
126040
126041     /**
126042      * @cfg {Mixed} menu
126043      * Either an instance of {@link Ext.menu.Menu} or a config object for an {@link Ext.menu.Menu}
126044      * which will act as a sub-menu to this item.
126045      * @markdown
126046      * @property {Ext.menu.Menu} menu The sub-menu associated with this item, if one was configured.
126047      */
126048
126049     /**
126050      * @cfg {String} menuAlign
126051      * The default {@link Ext.Element#getAlignToXY Ext.Element.getAlignToXY} anchor position value for this
126052      * item's sub-menu relative to this item's position. Defaults to `'tl-tr?'`.
126053      * @markdown
126054      */
126055     menuAlign: 'tl-tr?',
126056
126057     /**
126058      * @cfg {Number} menuExpandDelay
126059      * The delay in milliseconds before this item's sub-menu expands after this item is moused over. Defaults to `200`.
126060      * @markdown
126061      */
126062     menuExpandDelay: 200,
126063
126064     /**
126065      * @cfg {Number} menuHideDelay
126066      * The delay in milliseconds before this item's sub-menu hides after this item is moused out. Defaults to `200`.
126067      * @markdown
126068      */
126069     menuHideDelay: 200,
126070
126071     /**
126072      * @cfg {Boolean} plain
126073      * Whether or not this item is plain text/html with no icon or visual activation. Defaults to `false`.
126074      * @markdown
126075      */
126076
126077     renderTpl: [
126078         '<tpl if="plain">',
126079             '{text}',
126080         '</tpl>',
126081         '<tpl if="!plain">',
126082             '<a id="{id}-itemEl" class="' + Ext.baseCSSPrefix + 'menu-item-link" href="{href}" <tpl if="hrefTarget">target="{hrefTarget}"</tpl> hidefocus="true" unselectable="on">',
126083                 '<img id="{id}-iconEl" src="{icon}" class="' + Ext.baseCSSPrefix + 'menu-item-icon {iconCls}" />',
126084                 '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'menu-item-text" <tpl if="menu">style="margin-right: 17px;"</tpl> >{text}</span>',
126085                 '<tpl if="menu">',
126086                     '<img id="{id}-arrowEl" src="{blank}" class="' + Ext.baseCSSPrefix + 'menu-item-arrow" />',
126087                 '</tpl>',
126088             '</a>',
126089         '</tpl>'
126090     ],
126091
126092     maskOnDisable: false,
126093
126094     /**
126095      * @cfg {String} text
126096      * The text/html to display in this item. Defaults to `undefined`.
126097      * @markdown
126098      */
126099
126100     activate: function() {
126101         var me = this;
126102
126103         if (!me.activated && me.canActivate && me.rendered && !me.isDisabled() && me.isVisible()) {
126104             me.el.addCls(me.activeCls);
126105             me.focus();
126106             me.activated = true;
126107             me.fireEvent('activate', me);
126108         }
126109     },
126110
126111     blur: function() {
126112         this.$focused = false;
126113         this.callParent(arguments);
126114     },
126115
126116     deactivate: function() {
126117         var me = this;
126118
126119         if (me.activated) {
126120             me.el.removeCls(me.activeCls);
126121             me.blur();
126122             me.hideMenu();
126123             me.activated = false;
126124             me.fireEvent('deactivate', me);
126125         }
126126     },
126127
126128     deferExpandMenu: function() {
126129         var me = this;
126130
126131         if (!me.menu.rendered || !me.menu.isVisible()) {
126132             me.parentMenu.activeChild = me.menu;
126133             me.menu.parentItem = me;
126134             me.menu.parentMenu = me.menu.ownerCt = me.parentMenu;
126135             me.menu.showBy(me, me.menuAlign);
126136         }
126137     },
126138
126139     deferHideMenu: function() {
126140         if (this.menu.isVisible()) {
126141             this.menu.hide();
126142         }
126143     },
126144
126145     deferHideParentMenus: function() {
126146         Ext.menu.Manager.hideAll();
126147     },
126148
126149     expandMenu: function(delay) {
126150         var me = this;
126151
126152         if (me.menu) {
126153             clearTimeout(me.hideMenuTimer);
126154             if (delay === 0) {
126155                 me.deferExpandMenu();
126156             } else {
126157                 me.expandMenuTimer = Ext.defer(me.deferExpandMenu, Ext.isNumber(delay) ? delay : me.menuExpandDelay, me);
126158             }
126159         }
126160     },
126161
126162     focus: function() {
126163         this.$focused = true;
126164         this.callParent(arguments);
126165     },
126166
126167     getRefItems: function(deep){
126168         var menu = this.menu,
126169             items;
126170
126171         if (menu) {
126172             items = menu.getRefItems(deep);
126173             items.unshift(menu);
126174         }
126175         return items || [];
126176     },
126177
126178     hideMenu: function(delay) {
126179         var me = this;
126180
126181         if (me.menu) {
126182             clearTimeout(me.expandMenuTimer);
126183             me.hideMenuTimer = Ext.defer(me.deferHideMenu, Ext.isNumber(delay) ? delay : me.menuHideDelay, me);
126184         }
126185     },
126186
126187     initComponent: function() {
126188         var me = this,
126189             prefix = Ext.baseCSSPrefix,
126190             cls = [prefix + 'menu-item'];
126191
126192         me.addEvents(
126193             /**
126194              * @event activate
126195              * Fires when this item is activated
126196              * @param {Ext.menu.Item} item The activated item
126197              */
126198             'activate',
126199
126200             /**
126201              * @event click
126202              * Fires when this item is clicked
126203              * @param {Ext.menu.Item} item The item that was clicked
126204              * @param {Ext.EventObject} e The underyling {@link Ext.EventObject}.
126205              */
126206             'click',
126207
126208             /**
126209              * @event deactivate
126210              * Fires when this tiem is deactivated
126211              * @param {Ext.menu.Item} item The deactivated item
126212              */
126213             'deactivate'
126214         );
126215
126216         if (me.plain) {
126217             cls.push(prefix + 'menu-item-plain');
126218         }
126219
126220         if (me.cls) {
126221             cls.push(me.cls);
126222         }
126223
126224         me.cls = cls.join(' ');
126225
126226         if (me.menu) {
126227             me.menu = Ext.menu.Manager.get(me.menu);
126228         }
126229
126230         me.callParent(arguments);
126231     },
126232
126233     onClick: function(e) {
126234         var me = this;
126235
126236         if (!me.href) {
126237             e.stopEvent();
126238         }
126239
126240         if (me.disabled) {
126241             return;
126242         }
126243
126244         if (me.hideOnClick) {
126245             me.deferHideParentMenusTimer = Ext.defer(me.deferHideParentMenus, me.clickHideDelay, me);
126246         }
126247
126248         Ext.callback(me.handler, me.scope || me, [me, e]);
126249         me.fireEvent('click', me, e);
126250
126251         if (!me.hideOnClick) {
126252             me.focus();
126253         }
126254     },
126255
126256     onDestroy: function() {
126257         var me = this;
126258
126259         clearTimeout(me.expandMenuTimer);
126260         clearTimeout(me.hideMenuTimer);
126261         clearTimeout(me.deferHideParentMenusTimer);
126262
126263         if (me.menu) {
126264             delete me.menu.parentItem;
126265             delete me.menu.parentMenu;
126266             delete me.menu.ownerCt;
126267             if (me.destroyMenu !== false) {
126268                 me.menu.destroy();
126269             }
126270         }
126271         me.callParent(arguments);
126272     },
126273
126274     onRender: function(ct, pos) {
126275         var me = this,
126276             blank = Ext.BLANK_IMAGE_URL;
126277
126278         Ext.applyIf(me.renderData, {
126279             href: me.href || '#',
126280             hrefTarget: me.hrefTarget,
126281             icon: me.icon || blank,
126282             iconCls: me.iconCls + (me.checkChangeDisabled ? ' ' + me.disabledCls : ''),
126283             menu: Ext.isDefined(me.menu),
126284             plain: me.plain,
126285             text: me.text,
126286             blank: blank
126287         });
126288
126289         me.addChildEls('itemEl', 'iconEl', 'textEl', 'arrowEl');
126290
126291         me.callParent(arguments);
126292     },
126293
126294     /**
126295      * Sets the {@link #click} handler of this item
126296      * @param {Function} fn The handler function
126297      * @param {Object} scope (optional) The scope of the handler function
126298      */
126299     setHandler: function(fn, scope) {
126300         this.handler = fn || null;
126301         this.scope = scope;
126302     },
126303
126304     /**
126305      * Sets the {@link #iconCls} of this item
126306      * @param {String} iconCls The CSS class to set to {@link #iconCls}
126307      */
126308     setIconCls: function(iconCls) {
126309         var me = this;
126310
126311         if (me.iconEl) {
126312             if (me.iconCls) {
126313                 me.iconEl.removeCls(me.iconCls);
126314             }
126315
126316             if (iconCls) {
126317                 me.iconEl.addCls(iconCls);
126318             }
126319         }
126320
126321         me.iconCls = iconCls;
126322     },
126323
126324     /**
126325      * Sets the {@link #text} of this item
126326      * @param {String} text The {@link #text}
126327      */
126328     setText: function(text) {
126329         var me = this,
126330             el = me.textEl || me.el;
126331
126332         me.text = text;
126333
126334         if (me.rendered) {
126335             el.update(text || '');
126336             // cannot just call doComponentLayout due to stretchmax
126337             me.ownerCt.redoComponentLayout();
126338         }
126339     }
126340 });
126341
126342 /**
126343  * A menu item that contains a togglable checkbox by default, but that can also be a part of a radio group.
126344  *
126345  *     @example
126346  *     Ext.create('Ext.menu.Menu', {
126347  *         width: 100,
126348  *         height: 110,
126349  *         floating: false,  // usually you want this set to True (default)
126350  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126351  *         items: [{
126352  *             xtype: 'menucheckitem',
126353  *             text: 'select all'
126354  *         },{
126355  *             xtype: 'menucheckitem',
126356  *             text: 'select specific',
126357  *         },{
126358  *             iconCls: 'add16',
126359  *             text: 'icon item'
126360  *         },{
126361  *             text: 'regular item'
126362  *         }]
126363  *     });
126364  */
126365 Ext.define('Ext.menu.CheckItem', {
126366     extend: 'Ext.menu.Item',
126367     alias: 'widget.menucheckitem',
126368
126369     /**
126370      * @cfg {String} checkedCls
126371      * The CSS class used by {@link #cls} to show the checked state.
126372      * Defaults to `Ext.baseCSSPrefix + 'menu-item-checked'`.
126373      */
126374     checkedCls: Ext.baseCSSPrefix + 'menu-item-checked',
126375     /**
126376      * @cfg {String} uncheckedCls
126377      * The CSS class used by {@link #cls} to show the unchecked state.
126378      * Defaults to `Ext.baseCSSPrefix + 'menu-item-unchecked'`.
126379      */
126380     uncheckedCls: Ext.baseCSSPrefix + 'menu-item-unchecked',
126381     /**
126382      * @cfg {String} groupCls
126383      * The CSS class applied to this item's icon image to denote being a part of a radio group.
126384      * Defaults to `Ext.baseCSSClass + 'menu-group-icon'`.
126385      * Any specified {@link #iconCls} overrides this.
126386      */
126387     groupCls: Ext.baseCSSPrefix + 'menu-group-icon',
126388
126389     /**
126390      * @cfg {Boolean} hideOnClick
126391      * Whether to not to hide the owning menu when this item is clicked.
126392      * Defaults to `false` for checkbox items, and to `true` for radio group items.
126393      */
126394     hideOnClick: false,
126395
126396     afterRender: function() {
126397         var me = this;
126398         this.callParent();
126399         me.checked = !me.checked;
126400         me.setChecked(!me.checked, true);
126401     },
126402
126403     initComponent: function() {
126404         var me = this;
126405         me.addEvents(
126406             /**
126407              * @event beforecheckchange
126408              * Fires before a change event. Return false to cancel.
126409              * @param {Ext.menu.CheckItem} this
126410              * @param {Boolean} checked
126411              */
126412             'beforecheckchange',
126413
126414             /**
126415              * @event checkchange
126416              * Fires after a change event.
126417              * @param {Ext.menu.CheckItem} this
126418              * @param {Boolean} checked
126419              */
126420             'checkchange'
126421         );
126422
126423         me.callParent(arguments);
126424
126425         Ext.menu.Manager.registerCheckable(me);
126426
126427         if (me.group) {
126428             if (!me.iconCls) {
126429                 me.iconCls = me.groupCls;
126430             }
126431             if (me.initialConfig.hideOnClick !== false) {
126432                 me.hideOnClick = true;
126433             }
126434         }
126435     },
126436
126437     /**
126438      * Disables just the checkbox functionality of this menu Item. If this menu item has a submenu, that submenu
126439      * will still be accessible
126440      */
126441     disableCheckChange: function() {
126442         var me = this;
126443
126444         if (me.iconEl) {
126445             me.iconEl.addCls(me.disabledCls);
126446         }
126447         me.checkChangeDisabled = true;
126448     },
126449
126450     /**
126451      * Reenables the checkbox functionality of this menu item after having been disabled by {@link #disableCheckChange}
126452      */
126453     enableCheckChange: function() {
126454         var me = this;
126455
126456         me.iconEl.removeCls(me.disabledCls);
126457         me.checkChangeDisabled = false;
126458     },
126459
126460     onClick: function(e) {
126461         var me = this;
126462         if(!me.disabled && !me.checkChangeDisabled && !(me.checked && me.group)) {
126463             me.setChecked(!me.checked);
126464         }
126465         this.callParent([e]);
126466     },
126467
126468     onDestroy: function() {
126469         Ext.menu.Manager.unregisterCheckable(this);
126470         this.callParent(arguments);
126471     },
126472
126473     /**
126474      * Sets the checked state of the item
126475      * @param {Boolean} checked True to check, false to uncheck
126476      * @param {Boolean} suppressEvents (optional) True to prevent firing the checkchange events. Defaults to `false`.
126477      */
126478     setChecked: function(checked, suppressEvents) {
126479         var me = this;
126480         if (me.checked !== checked && (suppressEvents || me.fireEvent('beforecheckchange', me, checked) !== false)) {
126481             if (me.el) {
126482                 me.el[checked  ? 'addCls' : 'removeCls'](me.checkedCls)[!checked ? 'addCls' : 'removeCls'](me.uncheckedCls);
126483             }
126484             me.checked = checked;
126485             Ext.menu.Manager.onCheckChange(me, checked);
126486             if (!suppressEvents) {
126487                 Ext.callback(me.checkHandler, me.scope, [me, checked]);
126488                 me.fireEvent('checkchange', me, checked);
126489             }
126490         }
126491     }
126492 });
126493
126494 /**
126495  * @class Ext.menu.KeyNav
126496  * @private
126497  */
126498 Ext.define('Ext.menu.KeyNav', {
126499     extend: 'Ext.util.KeyNav',
126500
126501     requires: ['Ext.FocusManager'],
126502     
126503     constructor: function(menu) {
126504         var me = this;
126505
126506         me.menu = menu;
126507         me.callParent([menu.el, {
126508             down: me.down,
126509             enter: me.enter,
126510             esc: me.escape,
126511             left: me.left,
126512             right: me.right,
126513             space: me.enter,
126514             tab: me.tab,
126515             up: me.up
126516         }]);
126517     },
126518
126519     down: function(e) {
126520         var me = this,
126521             fi = me.menu.focusedItem;
126522
126523         if (fi && e.getKey() == Ext.EventObject.DOWN && me.isWhitelisted(fi)) {
126524             return true;
126525         }
126526         me.focusNextItem(1);
126527     },
126528
126529     enter: function(e) {
126530         var menu = this.menu,
126531             focused = menu.focusedItem;
126532  
126533         if (menu.activeItem) {
126534             menu.onClick(e);
126535         } else if (focused && focused.isFormField) {
126536             // prevent stopEvent being called
126537             return true;
126538         }
126539     },
126540
126541     escape: function(e) {
126542         Ext.menu.Manager.hideAll();
126543     },
126544
126545     focusNextItem: function(step) {
126546         var menu = this.menu,
126547             items = menu.items,
126548             focusedItem = menu.focusedItem,
126549             startIdx = focusedItem ? items.indexOf(focusedItem) : -1,
126550             idx = startIdx + step;
126551
126552         while (idx != startIdx) {
126553             if (idx < 0) {
126554                 idx = items.length - 1;
126555             } else if (idx >= items.length) {
126556                 idx = 0;
126557             }
126558
126559             var item = items.getAt(idx);
126560             if (menu.canActivateItem(item)) {
126561                 menu.setActiveItem(item);
126562                 break;
126563             }
126564             idx += step;
126565         }
126566     },
126567
126568     isWhitelisted: function(item) {
126569         return Ext.FocusManager.isWhitelisted(item);
126570     },
126571
126572     left: function(e) {
126573         var menu = this.menu,
126574             fi = menu.focusedItem,
126575             ai = menu.activeItem;
126576
126577         if (fi && this.isWhitelisted(fi)) {
126578             return true;
126579         }
126580
126581         menu.hide();
126582         if (menu.parentMenu) {
126583             menu.parentMenu.focus();
126584         }
126585     },
126586
126587     right: function(e) {
126588         var menu = this.menu,
126589             fi = menu.focusedItem,
126590             ai = menu.activeItem,
126591             am;
126592
126593         if (fi && this.isWhitelisted(fi)) {
126594             return true;
126595         }
126596
126597         if (ai) {
126598             am = menu.activeItem.menu;
126599             if (am) {
126600                 ai.expandMenu(0);
126601                 Ext.defer(function() {
126602                     am.setActiveItem(am.items.getAt(0));
126603                 }, 25);
126604             }
126605         }
126606     },
126607
126608     tab: function(e) {
126609         var me = this;
126610
126611         if (e.shiftKey) {
126612             me.up(e);
126613         } else {
126614             me.down(e);
126615         }
126616     },
126617
126618     up: function(e) {
126619         var me = this,
126620             fi = me.menu.focusedItem;
126621
126622         if (fi && e.getKey() == Ext.EventObject.UP && me.isWhitelisted(fi)) {
126623             return true;
126624         }
126625         me.focusNextItem(-1);
126626     }
126627 });
126628 /**
126629  * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
126630  * add one of these by using "-" in your call to add() or in your items config rather than creating one directly.
126631  *
126632  *     @example
126633  *     Ext.create('Ext.menu.Menu', {
126634  *         width: 100,
126635  *         height: 100,
126636  *         floating: false,  // usually you want this set to True (default)
126637  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126638  *         items: [{
126639  *             text: 'icon item',
126640  *             iconCls: 'add16'
126641  *         },{
126642  *             xtype: 'menuseparator'
126643  *         },{
126644  *            text: 'seperator above',
126645  *         },{
126646  *            text: 'regular item',
126647  *         }]
126648  *     });
126649  */
126650 Ext.define('Ext.menu.Separator', {
126651     extend: 'Ext.menu.Item',
126652     alias: 'widget.menuseparator',
126653
126654     /**
126655      * @cfg {String} activeCls @hide
126656      */
126657
126658     /**
126659      * @cfg {Boolean} canActivate @hide
126660      */
126661     canActivate: false,
126662
126663     /**
126664      * @cfg {Boolean} clickHideDelay @hide
126665      */
126666
126667     /**
126668      * @cfg {Boolean} destroyMenu @hide
126669      */
126670
126671     /**
126672      * @cfg {Boolean} disabledCls @hide
126673      */
126674
126675     focusable: false,
126676
126677     /**
126678      * @cfg {String} href @hide
126679      */
126680
126681     /**
126682      * @cfg {String} hrefTarget @hide
126683      */
126684
126685     /**
126686      * @cfg {Boolean} hideOnClick @hide
126687      */
126688     hideOnClick: false,
126689
126690     /**
126691      * @cfg {String} icon @hide
126692      */
126693
126694     /**
126695      * @cfg {String} iconCls @hide
126696      */
126697
126698     /**
126699      * @cfg {Object} menu @hide
126700      */
126701
126702     /**
126703      * @cfg {String} menuAlign @hide
126704      */
126705
126706     /**
126707      * @cfg {Number} menuExpandDelay @hide
126708      */
126709
126710     /**
126711      * @cfg {Number} menuHideDelay @hide
126712      */
126713
126714     /**
126715      * @cfg {Boolean} plain @hide
126716      */
126717     plain: true,
126718
126719     /**
126720      * @cfg {String} separatorCls
126721      * The CSS class used by the separator item to show the incised line.
126722      * Defaults to `Ext.baseCSSPrefix + 'menu-item-separator'`.
126723      */
126724     separatorCls: Ext.baseCSSPrefix + 'menu-item-separator',
126725
126726     /**
126727      * @cfg {String} text @hide
126728      */
126729     text: '&#160;',
126730
126731     onRender: function(ct, pos) {
126732         var me = this,
126733             sepCls = me.separatorCls;
126734
126735         me.cls += ' ' + sepCls;
126736
126737         me.callParent(arguments);
126738     }
126739 });
126740 /**
126741  * A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}.
126742  *
126743  * Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}.
126744  * Menus may also contain {@link Ext.panel.AbstractPanel#dockedItems docked items} because it extends {@link Ext.panel.Panel}.
126745  *
126746  * To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items},
126747  * specify `{@link Ext.menu.Item#plain plain}: true`. This reserves a space for an icon, and indents the Component
126748  * in line with the other menu items.
126749  *
126750  * By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}: false`,
126751  * a Menu may be used as a child of a {@link Ext.container.Container Container}.
126752  *
126753  *     @example
126754  *     Ext.create('Ext.menu.Menu', {
126755  *         width: 100,
126756  *         height: 100,
126757  *         margin: '0 0 10 0',
126758  *         floating: false,  // usually you want this set to True (default)
126759  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126760  *         items: [{
126761  *             text: 'regular item 1'
126762  *         },{
126763  *             text: 'regular item 2'
126764  *         },{
126765  *             text: 'regular item 3'
126766  *         }]
126767  *     });
126768  *
126769  *     Ext.create('Ext.menu.Menu', {
126770  *         width: 100,
126771  *         height: 100,
126772  *         plain: true,
126773  *         floating: false,  // usually you want this set to True (default)
126774  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126775  *         items: [{
126776  *             text: 'plain item 1'
126777  *         },{
126778  *             text: 'plain item 2'
126779  *         },{
126780  *             text: 'plain item 3'
126781  *         }]
126782  *     });
126783  */
126784 Ext.define('Ext.menu.Menu', {
126785     extend: 'Ext.panel.Panel',
126786     alias: 'widget.menu',
126787     requires: [
126788         'Ext.layout.container.Fit',
126789         'Ext.layout.container.VBox',
126790         'Ext.menu.CheckItem',
126791         'Ext.menu.Item',
126792         'Ext.menu.KeyNav',
126793         'Ext.menu.Manager',
126794         'Ext.menu.Separator'
126795     ],
126796
126797     /**
126798      * @property {Ext.menu.Menu} parentMenu
126799      * The parent Menu of this Menu.
126800      */
126801
126802     /**
126803      * @cfg {Boolean} allowOtherMenus
126804      * True to allow multiple menus to be displayed at the same time.
126805      */
126806     allowOtherMenus: false,
126807
126808     /**
126809      * @cfg {String} ariaRole @hide
126810      */
126811     ariaRole: 'menu',
126812
126813     /**
126814      * @cfg {Boolean} autoRender @hide
126815      * floating is true, so autoRender always happens
126816      */
126817
126818     /**
126819      * @cfg {String} defaultAlign
126820      * The default {@link Ext.Element#getAlignToXY Ext.Element#getAlignToXY} anchor position value for this menu
126821      * relative to its element of origin.
126822      */
126823     defaultAlign: 'tl-bl?',
126824
126825     /**
126826      * @cfg {Boolean} floating
126827      * A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,
126828      * {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be
126829      * used as a child item of another {@link Ext.container.Container Container}.
126830      */
126831     floating: true,
126832
126833     /**
126834      * @cfg {Boolean} @hide
126835      * Menus are constrained to the document body by default
126836      */
126837     constrain: true,
126838
126839     /**
126840      * @cfg {Boolean} [hidden=undefined]
126841      * True to initially render the Menu as hidden, requiring to be shown manually.
126842      *
126843      * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
126844      */
126845     hidden: true,
126846
126847     hideMode: 'visibility',
126848
126849     /**
126850      * @cfg {Boolean} ignoreParentClicks
126851      * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
126852      * so that the submenu is not dismissed when clicking the parent item.
126853      */
126854     ignoreParentClicks: false,
126855
126856     isMenu: true,
126857
126858     /**
126859      * @cfg {String/Object} layout @hide
126860      */
126861
126862     /**
126863      * @cfg {Boolean} showSeparator
126864      * True to show the icon separator.
126865      */
126866     showSeparator : true,
126867
126868     /**
126869      * @cfg {Number} minWidth
126870      * The minimum width of the Menu.
126871      */
126872     minWidth: 120,
126873
126874     /**
126875      * @cfg {Boolean} [plain=false]
126876      * True to remove the incised line down the left side of the menu and to not indent general Component items.
126877      */
126878
126879     initComponent: function() {
126880         var me = this,
126881             prefix = Ext.baseCSSPrefix,
126882             cls = [prefix + 'menu'],
126883             bodyCls = me.bodyCls ? [me.bodyCls] : [];
126884
126885         me.addEvents(
126886             /**
126887              * @event click
126888              * Fires when this menu is clicked
126889              * @param {Ext.menu.Menu} menu The menu which has been clicked
126890              * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
126891              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.
126892              */
126893             'click',
126894
126895             /**
126896              * @event mouseenter
126897              * Fires when the mouse enters this menu
126898              * @param {Ext.menu.Menu} menu The menu
126899              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126900              */
126901             'mouseenter',
126902
126903             /**
126904              * @event mouseleave
126905              * Fires when the mouse leaves this menu
126906              * @param {Ext.menu.Menu} menu The menu
126907              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126908              */
126909             'mouseleave',
126910
126911             /**
126912              * @event mouseover
126913              * Fires when the mouse is hovering over this menu
126914              * @param {Ext.menu.Menu} menu The menu
126915              * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
126916              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126917              */
126918             'mouseover'
126919         );
126920
126921         Ext.menu.Manager.register(me);
126922
126923         // Menu classes
126924         if (me.plain) {
126925             cls.push(prefix + 'menu-plain');
126926         }
126927         me.cls = cls.join(' ');
126928
126929         // Menu body classes
126930         bodyCls.unshift(prefix + 'menu-body');
126931         me.bodyCls = bodyCls.join(' ');
126932
126933         // Internal vbox layout, with scrolling overflow
126934         // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller
126935         // options if we wish to allow for such configurations on the Menu.
126936         // e.g., scrolling speed, vbox align stretch, etc.
126937         me.layout = {
126938             type: 'vbox',
126939             align: 'stretchmax',
126940             autoSize: true,
126941             clearInnerCtOnLayout: true,
126942             overflowHandler: 'Scroller'
126943         };
126944
126945         // hidden defaults to false if floating is configured as false
126946         if (me.floating === false && me.initialConfig.hidden !== true) {
126947             me.hidden = false;
126948         }
126949
126950         me.callParent(arguments);
126951
126952         me.on('beforeshow', function() {
126953             var hasItems = !!me.items.length;
126954             // FIXME: When a menu has its show cancelled because of no items, it
126955             // gets a visibility: hidden applied to it (instead of the default display: none)
126956             // Not sure why, but we remove this style when we want to show again.
126957             if (hasItems && me.rendered) {
126958                 me.el.setStyle('visibility', null);
126959             }
126960             return hasItems;
126961         });
126962     },
126963
126964     afterRender: function(ct) {
126965         var me = this,
126966             prefix = Ext.baseCSSPrefix,
126967             space = '&#160;';
126968
126969         me.callParent(arguments);
126970
126971         // TODO: Move this to a subTemplate When we support them in the future
126972         if (me.showSeparator) {
126973             me.iconSepEl = me.layout.getRenderTarget().insertFirst({
126974                 cls: prefix + 'menu-icon-separator',
126975                 html: space
126976             });
126977         }
126978
126979         me.focusEl = me.el.createChild({
126980             cls: prefix + 'menu-focus',
126981             tabIndex: '-1',
126982             html: space
126983         });
126984
126985         me.mon(me.el, {
126986             click: me.onClick,
126987             mouseover: me.onMouseOver,
126988             scope: me
126989         });
126990         me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
126991
126992         if (me.showSeparator && ((!Ext.isStrict && Ext.isIE) || Ext.isIE6)) {
126993             me.iconSepEl.setHeight(me.el.getHeight());
126994         }
126995
126996         me.keyNav = Ext.create('Ext.menu.KeyNav', me);
126997     },
126998
126999     afterLayout: function() {
127000         var me = this;
127001         me.callParent(arguments);
127002
127003         // For IE6 & IE quirks, we have to resize the el and body since position: absolute
127004         // floating elements inherit their parent's width, making them the width of
127005         // document.body instead of the width of their contents.
127006         // This includes left/right dock items.
127007         if ((!Ext.isStrict && Ext.isIE) || Ext.isIE6) {
127008             var innerCt = me.layout.getRenderTarget(),
127009                 innerCtWidth = 0,
127010                 dis = me.dockedItems,
127011                 l = dis.length,
127012                 i = 0,
127013                 di, clone, newWidth;
127014
127015             innerCtWidth = innerCt.getWidth();
127016
127017             newWidth = innerCtWidth + me.body.getBorderWidth('lr') + me.body.getPadding('lr');
127018
127019             // First set the body to the new width
127020             me.body.setWidth(newWidth);
127021
127022             // Now we calculate additional width (docked items) and set the el's width
127023             for (; i < l, di = dis.getAt(i); i++) {
127024                 if (di.dock == 'left' || di.dock == 'right') {
127025                     newWidth += di.getWidth();
127026                 }
127027             }
127028             me.el.setWidth(newWidth);
127029         }
127030     },
127031     
127032     getBubbleTarget: function(){
127033         return this.parentMenu || this.callParent();
127034     },
127035
127036     /**
127037      * Returns whether a menu item can be activated or not.
127038      * @return {Boolean}
127039      */
127040     canActivateItem: function(item) {
127041         return item && !item.isDisabled() && item.isVisible() && (item.canActivate || item.getXTypes().indexOf('menuitem') < 0);
127042     },
127043
127044     /**
127045      * Deactivates the current active item on the menu, if one exists.
127046      */
127047     deactivateActiveItem: function() {
127048         var me = this;
127049
127050         if (me.activeItem) {
127051             me.activeItem.deactivate();
127052             if (!me.activeItem.activated) {
127053                 delete me.activeItem;
127054             }
127055         }
127056
127057         // only blur if focusedItem is not a filter
127058         if (me.focusedItem && !me.filtered) {
127059             me.focusedItem.blur();
127060             if (!me.focusedItem.$focused) {
127061                 delete me.focusedItem;
127062             }
127063         }
127064     },
127065
127066     clearStretch: function () {
127067         // the vbox/stretchmax will set the el sizes and subsequent layouts will not
127068         // reconsider them unless we clear the dimensions on the el's here:
127069         if (this.rendered) {
127070             this.items.each(function (item) {
127071                 // each menuItem component needs to layout again, so clear its cache
127072                 if (item.componentLayout) {
127073                     delete item.componentLayout.lastComponentSize;
127074                 }
127075                 if (item.el) {
127076                     item.el.setWidth(null);
127077                 }
127078             });
127079         }
127080     },
127081
127082     onAdd: function () {
127083         var me = this;
127084
127085         me.clearStretch();
127086         me.callParent(arguments);
127087
127088         if (Ext.isIE6 || Ext.isIE7) {
127089             // TODO - why does this need to be done (and not ok to do now)?
127090             Ext.Function.defer(me.doComponentLayout, 10, me);
127091         }
127092     },
127093
127094     onRemove: function () {
127095         this.clearStretch();
127096         this.callParent(arguments);
127097
127098     },
127099
127100     redoComponentLayout: function () {
127101         if (this.rendered) {
127102             this.clearStretch();
127103             this.doComponentLayout();
127104         }
127105     },
127106
127107     // inherit docs
127108     getFocusEl: function() {
127109         return this.focusEl;
127110     },
127111
127112     // inherit docs
127113     hide: function() {
127114         this.deactivateActiveItem();
127115         this.callParent(arguments);
127116     },
127117
127118     // private
127119     getItemFromEvent: function(e) {
127120         return this.getChildByElement(e.getTarget());
127121     },
127122
127123     lookupComponent: function(cmp) {
127124         var me = this;
127125
127126         if (Ext.isString(cmp)) {
127127             cmp = me.lookupItemFromString(cmp);
127128         } else if (Ext.isObject(cmp)) {
127129             cmp = me.lookupItemFromObject(cmp);
127130         }
127131
127132         // Apply our minWidth to all of our child components so it's accounted
127133         // for in our VBox layout
127134         cmp.minWidth = cmp.minWidth || me.minWidth;
127135
127136         return cmp;
127137     },
127138
127139     // private
127140     lookupItemFromObject: function(cmp) {
127141         var me = this,
127142             prefix = Ext.baseCSSPrefix,
127143             cls,
127144             intercept;
127145
127146         if (!cmp.isComponent) {
127147             if (!cmp.xtype) {
127148                 cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
127149             } else {
127150                 cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
127151             }
127152         }
127153
127154         if (cmp.isMenuItem) {
127155             cmp.parentMenu = me;
127156         }
127157
127158         if (!cmp.isMenuItem && !cmp.dock) {
127159             cls = [prefix + 'menu-item', prefix + 'menu-item-cmp'];
127160             intercept = Ext.Function.createInterceptor;
127161
127162             // Wrap focus/blur to control component focus
127163             cmp.focus = intercept(cmp.focus, function() {
127164                 this.$focused = true;
127165             }, cmp);
127166             cmp.blur = intercept(cmp.blur, function() {
127167                 this.$focused = false;
127168             }, cmp);
127169
127170             if (!me.plain && (cmp.indent === true || cmp.iconCls === 'no-icon')) {
127171                 cls.push(prefix + 'menu-item-indent');
127172             }
127173
127174             if (cmp.rendered) {
127175                 cmp.el.addCls(cls);
127176             } else {
127177                 cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
127178             }
127179             cmp.isMenuItem = true;
127180         }
127181         return cmp;
127182     },
127183
127184     // private
127185     lookupItemFromString: function(cmp) {
127186         return (cmp == 'separator' || cmp == '-') ?
127187             Ext.createWidget('menuseparator')
127188             : Ext.createWidget('menuitem', {
127189                 canActivate: false,
127190                 hideOnClick: false,
127191                 plain: true,
127192                 text: cmp
127193             });
127194     },
127195
127196     onClick: function(e) {
127197         var me = this,
127198             item;
127199
127200         if (me.disabled) {
127201             e.stopEvent();
127202             return;
127203         }
127204
127205         if ((e.getTarget() == me.focusEl.dom) || e.within(me.layout.getRenderTarget())) {
127206             item = me.getItemFromEvent(e) || me.activeItem;
127207
127208             if (item) {
127209                 if (item.getXTypes().indexOf('menuitem') >= 0) {
127210                     if (!item.menu || !me.ignoreParentClicks) {
127211                         item.onClick(e);
127212                     } else {
127213                         e.stopEvent();
127214                     }
127215                 }
127216             }
127217             me.fireEvent('click', me, item, e);
127218         }
127219     },
127220
127221     onDestroy: function() {
127222         var me = this;
127223
127224         Ext.menu.Manager.unregister(me);
127225         if (me.rendered) {
127226             me.el.un(me.mouseMonitor);
127227             me.keyNav.destroy();
127228             delete me.keyNav;
127229         }
127230         me.callParent(arguments);
127231     },
127232
127233     onMouseLeave: function(e) {
127234         var me = this;
127235
127236         me.deactivateActiveItem();
127237
127238         if (me.disabled) {
127239             return;
127240         }
127241
127242         me.fireEvent('mouseleave', me, e);
127243     },
127244
127245     onMouseOver: function(e) {
127246         var me = this,
127247             fromEl = e.getRelatedTarget(),
127248             mouseEnter = !me.el.contains(fromEl),
127249             item = me.getItemFromEvent(e);
127250
127251         if (mouseEnter && me.parentMenu) {
127252             me.parentMenu.setActiveItem(me.parentItem);
127253             me.parentMenu.mouseMonitor.mouseenter();
127254         }
127255
127256         if (me.disabled) {
127257             return;
127258         }
127259
127260         if (item) {
127261             me.setActiveItem(item);
127262             if (item.activated && item.expandMenu) {
127263                 item.expandMenu();
127264             }
127265         }
127266         if (mouseEnter) {
127267             me.fireEvent('mouseenter', me, e);
127268         }
127269         me.fireEvent('mouseover', me, item, e);
127270     },
127271
127272     setActiveItem: function(item) {
127273         var me = this;
127274
127275         if (item && (item != me.activeItem && item != me.focusedItem)) {
127276             me.deactivateActiveItem();
127277             if (me.canActivateItem(item)) {
127278                 if (item.activate) {
127279                     item.activate();
127280                     if (item.activated) {
127281                         me.activeItem = item;
127282                         me.focusedItem = item;
127283                         me.focus();
127284                     }
127285                 } else {
127286                     item.focus();
127287                     me.focusedItem = item;
127288                 }
127289             }
127290             item.el.scrollIntoView(me.layout.getRenderTarget());
127291         }
127292     },
127293
127294     /**
127295      * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.Element Element}.
127296      * @param {Ext.Component/Ext.Element} component The {@link Ext.Component} or {@link Ext.Element} to show the menu by.
127297      * @param {String} position (optional) Alignment position as used by {@link Ext.Element#getAlignToXY}.
127298      * Defaults to `{@link #defaultAlign}`.
127299      * @param {Number[]} offsets (optional) Alignment offsets as used by {@link Ext.Element#getAlignToXY}. Defaults to `undefined`.
127300      * @return {Ext.menu.Menu} This Menu.
127301      */
127302     showBy: function(cmp, pos, off) {
127303         var me = this,
127304             xy,
127305             region;
127306
127307         if (me.floating && cmp) {
127308             me.layout.autoSize = true;
127309
127310             // show off-screen first so that we can calc position without causing a visual jump
127311             me.doAutoRender();
127312             delete me.needsLayout;
127313
127314             // Component or Element
127315             cmp = cmp.el || cmp;
127316
127317             // Convert absolute to floatParent-relative coordinates if necessary.
127318             xy = me.el.getAlignToXY(cmp, pos || me.defaultAlign, off);
127319             if (me.floatParent) {
127320                 region = me.floatParent.getTargetEl().getViewRegion();
127321                 xy[0] -= region.x;
127322                 xy[1] -= region.y;
127323             }
127324             me.showAt(xy);
127325         }
127326         return me;
127327     },
127328
127329     doConstrain : function() {
127330         var me = this,
127331             y = me.el.getY(),
127332             max, full,
127333             vector,
127334             returnY = y, normalY, parentEl, scrollTop, viewHeight;
127335
127336         delete me.height;
127337         me.setSize();
127338         full = me.getHeight();
127339         if (me.floating) {
127340             //if our reset css is scoped, there will be a x-reset wrapper on this menu which we need to skip
127341             parentEl = Ext.fly(me.el.getScopeParent());
127342             scrollTop = parentEl.getScroll().top;
127343             viewHeight = parentEl.getViewSize().height;
127344             //Normalize y by the scroll position for the parent element.  Need to move it into the coordinate space
127345             //of the view.
127346             normalY = y - scrollTop;
127347             max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
127348             if (full > viewHeight) {
127349                 max = viewHeight;
127350                 //Set returnY equal to (0,0) in view space by reducing y by the value of normalY
127351                 returnY = y - normalY;
127352             } else if (max < full) {
127353                 returnY = y - (full - max);
127354                 max = full;
127355             }
127356         }else{
127357             max = me.getHeight();
127358         }
127359         // Always respect maxHeight
127360         if (me.maxHeight){
127361             max = Math.min(me.maxHeight, max);
127362         }
127363         if (full > max && max > 0){
127364             me.layout.autoSize = false;
127365             me.setHeight(max);
127366             if (me.showSeparator){
127367                 me.iconSepEl.setHeight(me.layout.getRenderTarget().dom.scrollHeight);
127368             }
127369         }
127370         vector = me.getConstrainVector(me.el.getScopeParent());
127371         if (vector) {
127372             me.setPosition(me.getPosition()[0] + vector[0]);
127373         }
127374         me.el.setY(returnY);
127375     }
127376 });
127377
127378 /**
127379  * A menu containing a Ext.picker.Color Component.
127380  *
127381  * Notes:
127382  *
127383  *   - Although not listed here, the **constructor** for this class accepts all of the
127384  *     configuration options of {@link Ext.picker.Color}.
127385  *   - If subclassing ColorMenu, any configuration options for the ColorPicker must be
127386  *     applied to the **initialConfig** property of the ColorMenu. Applying
127387  *     {@link Ext.picker.Color ColorPicker} configuration settings to `this` will **not**
127388  *     affect the ColorPicker's configuration.
127389  *
127390  * Example:
127391  *
127392  *     @example
127393  *     var colorPicker = Ext.create('Ext.menu.ColorPicker', {
127394  *         value: '000000'
127395  *     });
127396  *
127397  *     Ext.create('Ext.menu.Menu', {
127398  *         width: 100,
127399  *         height: 90,
127400  *         floating: false,  // usually you want this set to True (default)
127401  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
127402  *         items: [{
127403  *             text: 'choose a color',
127404  *             menu: colorPicker
127405  *         },{
127406  *             iconCls: 'add16',
127407  *             text: 'icon item'
127408  *         },{
127409  *             text: 'regular item'
127410  *         }]
127411  *     });
127412  */
127413  Ext.define('Ext.menu.ColorPicker', {
127414      extend: 'Ext.menu.Menu',
127415
127416      alias: 'widget.colormenu',
127417
127418      requires: [
127419         'Ext.picker.Color'
127420      ],
127421
127422     /**
127423      * @cfg {Boolean} hideOnClick
127424      * False to continue showing the menu after a date is selected.
127425      */
127426     hideOnClick : true,
127427
127428     /**
127429      * @cfg {String} pickerId
127430      * An id to assign to the underlying color picker.
127431      */
127432     pickerId : null,
127433
127434     /**
127435      * @cfg {Number} maxHeight
127436      * @hide
127437      */
127438
127439     /**
127440      * @property {Ext.picker.Color} picker
127441      * The {@link Ext.picker.Color} instance for this ColorMenu
127442      */
127443
127444     /**
127445      * @event click
127446      * @hide
127447      */
127448
127449     /**
127450      * @event itemclick
127451      * @hide
127452      */
127453
127454     initComponent : function(){
127455         var me = this,
127456             cfg = Ext.apply({}, me.initialConfig);
127457
127458         // Ensure we don't get duplicate listeners
127459         delete cfg.listeners;
127460         Ext.apply(me, {
127461             plain: true,
127462             showSeparator: false,
127463             items: Ext.applyIf({
127464                 cls: Ext.baseCSSPrefix + 'menu-color-item',
127465                 id: me.pickerId,
127466                 xtype: 'colorpicker'
127467             }, cfg)
127468         });
127469
127470         me.callParent(arguments);
127471
127472         me.picker = me.down('colorpicker');
127473
127474         /**
127475          * @event select
127476          * @alias Ext.picker.Color#select
127477          */
127478         me.relayEvents(me.picker, ['select']);
127479
127480         if (me.hideOnClick) {
127481             me.on('select', me.hidePickerOnSelect, me);
127482         }
127483     },
127484
127485     /**
127486      * Hides picker on select if hideOnClick is true
127487      * @private
127488      */
127489     hidePickerOnSelect: function() {
127490         Ext.menu.Manager.hideAll();
127491     }
127492  });
127493 /**
127494  * A menu containing an Ext.picker.Date Component.
127495  *
127496  * Notes:
127497  *
127498  * - Although not listed here, the **constructor** for this class accepts all of the
127499  *   configuration options of **{@link Ext.picker.Date}**.
127500  * - If subclassing DateMenu, any configuration options for the DatePicker must be applied
127501  *   to the **initialConfig** property of the DateMenu. Applying {@link Ext.picker.Date Date Picker}
127502  *   configuration settings to **this** will **not** affect the Date Picker's configuration.
127503  *
127504  * Example:
127505  *
127506  *     @example
127507  *     var dateMenu = Ext.create('Ext.menu.DatePicker', {
127508  *         handler: function(dp, date){
127509  *             Ext.Msg.alert('Date Selected', 'You selected ' + Ext.Date.format(date, 'M j, Y'));
127510  *         }
127511  *     });
127512  *
127513  *     Ext.create('Ext.menu.Menu', {
127514  *         width: 100,
127515  *         height: 90,
127516  *         floating: false,  // usually you want this set to True (default)
127517  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
127518  *         items: [{
127519  *             text: 'choose a date',
127520  *             menu: dateMenu
127521  *         },{
127522  *             iconCls: 'add16',
127523  *             text: 'icon item'
127524  *         },{
127525  *             text: 'regular item'
127526  *         }]
127527  *     });
127528  */
127529  Ext.define('Ext.menu.DatePicker', {
127530      extend: 'Ext.menu.Menu',
127531
127532      alias: 'widget.datemenu',
127533
127534      requires: [
127535         'Ext.picker.Date'
127536      ],
127537
127538     /**
127539      * @cfg {Boolean} hideOnClick
127540      * False to continue showing the menu after a date is selected.
127541      */
127542     hideOnClick : true,
127543
127544     /**
127545      * @cfg {String} pickerId
127546      * An id to assign to the underlying date picker.
127547      */
127548     pickerId : null,
127549
127550     /**
127551      * @cfg {Number} maxHeight
127552      * @hide
127553      */
127554
127555     /**
127556      * @property {Ext.picker.Date} picker
127557      * The {@link Ext.picker.Date} instance for this DateMenu
127558      */
127559
127560     /**
127561      * @event click
127562      * @hide
127563      */
127564
127565     /**
127566      * @event itemclick
127567      * @hide
127568      */
127569
127570     initComponent : function(){
127571         var me = this;
127572
127573         Ext.apply(me, {
127574             showSeparator: false,
127575             plain: true,
127576             border: false,
127577             bodyPadding: 0, // remove the body padding from the datepicker menu item so it looks like 3.3
127578             items: Ext.applyIf({
127579                 cls: Ext.baseCSSPrefix + 'menu-date-item',
127580                 id: me.pickerId,
127581                 xtype: 'datepicker'
127582             }, me.initialConfig)
127583         });
127584
127585         me.callParent(arguments);
127586
127587         me.picker = me.down('datepicker');
127588         /**
127589          * @event select
127590          * @alias Ext.picker.Date#select
127591          */
127592         me.relayEvents(me.picker, ['select']);
127593
127594         if (me.hideOnClick) {
127595             me.on('select', me.hidePickerOnSelect, me);
127596         }
127597     },
127598
127599     hidePickerOnSelect: function() {
127600         Ext.menu.Manager.hideAll();
127601     }
127602  });
127603 /**
127604  * This class is used to display small visual icons in the header of a panel. There are a set of
127605  * 25 icons that can be specified by using the {@link #type} config. The {@link #handler} config
127606  * can be used to provide a function that will respond to any click events. In general, this class
127607  * will not be instantiated directly, rather it will be created by specifying the {@link Ext.panel.Panel#tools}
127608  * configuration on the Panel itself.
127609  *
127610  *     @example
127611  *     Ext.create('Ext.panel.Panel', {
127612  *         width: 200,
127613  *         height: 200,
127614  *         renderTo: document.body,
127615  *         title: 'A Panel',
127616  *         tools: [{
127617  *             type: 'help',
127618  *             handler: function(){
127619  *                 // show help here
127620  *             }
127621  *         }, {
127622  *             itemId: 'refresh',
127623  *             type: 'refresh',
127624  *             hidden: true,
127625  *             handler: function(){
127626  *                 // do refresh
127627  *             }
127628  *         }, {
127629  *             type: 'search',
127630  *             handler: function(event, target, owner, tool){
127631  *                 // do search
127632  *                 owner.child('#refresh').show();
127633  *             }
127634  *         }]
127635  *     });
127636  */
127637 Ext.define('Ext.panel.Tool', {
127638     extend: 'Ext.Component',
127639     requires: ['Ext.tip.QuickTipManager'],
127640     alias: 'widget.tool',
127641
127642     baseCls: Ext.baseCSSPrefix + 'tool',
127643     disabledCls: Ext.baseCSSPrefix + 'tool-disabled',
127644     toolPressedCls: Ext.baseCSSPrefix + 'tool-pressed',
127645     toolOverCls: Ext.baseCSSPrefix + 'tool-over',
127646     ariaRole: 'button',
127647     renderTpl: ['<img id="{id}-toolEl" src="{blank}" class="{baseCls}-{type}" role="presentation"/>'],
127648
127649     /**
127650      * @cfg {Function} handler
127651      * A function to execute when the tool is clicked. Arguments passed are:
127652      *
127653      * - **event** : Ext.EventObject - The click event.
127654      * - **toolEl** : Ext.Element - The tool Element.
127655      * - **owner** : Ext.panel.Header - The host panel header.
127656      * - **tool** : Ext.panel.Tool - The tool object
127657      */
127658
127659     /**
127660      * @cfg {Object} scope
127661      * The scope to execute the {@link #handler} function. Defaults to the tool.
127662      */
127663
127664     /**
127665      * @cfg {String} type
127666      * The type of tool to render. The following types are available:
127667      *
127668      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-close"></span> close
127669      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minimize"></span> minimize
127670      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-maximize"></span> maximize
127671      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-restore"></span> restore
127672      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-toggle"></span> toggle
127673      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-gear"></span> gear
127674      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-prev"></span> prev
127675      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-next"></span> next
127676      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-pin"></span> pin
127677      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-unpin"></span> unpin
127678      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-right"></span> right
127679      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-left"></span> left
127680      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-down"></span> down
127681      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-up"></span> up
127682      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-refresh"></span> refresh
127683      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-plus"></span> plus
127684      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minus"></span> minus
127685      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-search"></span> search
127686      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-save"></span> save
127687      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-help"></span> help
127688      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-print"></span> print
127689      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-expand"></span> expand
127690      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-collapse"></span> collapse
127691      */
127692
127693     /**
127694      * @cfg {String/Object} tooltip
127695      * The tooltip for the tool - can be a string to be used as innerHTML (html tags are accepted) or QuickTips config
127696      * object
127697      */
127698
127699      /**
127700      * @cfg {String} tooltipType
127701      * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
127702      */
127703     tooltipType: 'qtip',
127704
127705     /**
127706      * @cfg {Boolean} stopEvent
127707      * Specify as false to allow click event to propagate.
127708      */
127709     stopEvent: true,
127710
127711     initComponent: function() {
127712         var me = this;
127713         me.addEvents(
127714             /**
127715              * @event click
127716              * Fires when the tool is clicked
127717              * @param {Ext.panel.Tool} this
127718              * @param {Ext.EventObject} e The event object
127719              */
127720             'click'
127721         );
127722
127723         var types = [
127724             'close',
127725             'collapse',
127726             'down',
127727             'expand',
127728             'gear',
127729             'help',
127730             'left',
127731             'maximize',
127732             'minimize',
127733             'minus',
127734             'move',
127735             'next',
127736             'pin',
127737             'plus',
127738             'prev',
127739             'print',
127740             'refresh',
127741             'resize',
127742             'restore',
127743             'right',
127744             'save',
127745             'search',
127746             'toggle',
127747             'unpin',
127748             'up'
127749         ];
127750
127751         if (me.id && Ext.Array.indexOf(types, me.id) > -1 && Ext.global.console) {
127752             Ext.global.console.warn('When specifying a tool you should use the type option, the id can conflict now that tool is a Component');
127753         }
127754
127755         me.type = me.type || me.id;
127756
127757         Ext.applyIf(me.renderData, {
127758             baseCls: me.baseCls,
127759             blank: Ext.BLANK_IMAGE_URL,
127760             type: me.type
127761         });
127762
127763         me.addChildEls('toolEl');
127764
127765         // alias qtip, should use tooltip since it's what we have in the docs
127766         me.tooltip = me.tooltip || me.qtip;
127767         me.callParent();
127768     },
127769
127770     // inherit docs
127771     afterRender: function() {
127772         var me = this,
127773             attr;
127774
127775         me.callParent(arguments);
127776         if (me.tooltip) {
127777             if (Ext.isObject(me.tooltip)) {
127778                 Ext.tip.QuickTipManager.register(Ext.apply({
127779                     target: me.id
127780                 }, me.tooltip));
127781             }
127782             else {
127783                 attr = me.tooltipType == 'qtip' ? 'data-qtip' : 'title';
127784                 me.toolEl.dom.setAttribute(attr, me.tooltip);
127785             }
127786         }
127787
127788         me.mon(me.toolEl, {
127789             click: me.onClick,
127790             mousedown: me.onMouseDown,
127791             mouseover: me.onMouseOver,
127792             mouseout: me.onMouseOut,
127793             scope: me
127794         });
127795     },
127796
127797     /**
127798      * Sets the type of the tool. Allows the icon to be changed.
127799      * @param {String} type The new type. See the {@link #type} config.
127800      * @return {Ext.panel.Tool} this
127801      */
127802     setType: function(type) {
127803         var me = this;
127804
127805         me.type = type;
127806         if (me.rendered) {
127807             me.toolEl.dom.className = me.baseCls + '-' + type;
127808         }
127809         return me;
127810     },
127811
127812     /**
127813      * Binds this tool to a component.
127814      * @private
127815      * @param {Ext.Component} component The component
127816      */
127817     bindTo: function(component) {
127818         this.owner = component;
127819     },
127820
127821     /**
127822      * Called when the tool element is clicked
127823      * @private
127824      * @param {Ext.EventObject} e
127825      * @param {HTMLElement} target The target element
127826      */
127827     onClick: function(e, target) {
127828         var me = this,
127829             owner;
127830
127831         if (me.disabled) {
127832             return false;
127833         }
127834         owner = me.owner || me.ownerCt;
127835
127836         //remove the pressed + over class
127837         me.el.removeCls(me.toolPressedCls);
127838         me.el.removeCls(me.toolOverCls);
127839
127840         if (me.stopEvent !== false) {
127841             e.stopEvent();
127842         }
127843
127844         Ext.callback(me.handler, me.scope || me, [e, target, owner, me]);
127845         me.fireEvent('click', me, e);
127846         return true;
127847     },
127848
127849     // inherit docs
127850     onDestroy: function(){
127851         if (Ext.isObject(this.tooltip)) {
127852             Ext.tip.QuickTipManager.unregister(this.id);
127853         }
127854         this.callParent();
127855     },
127856
127857     /**
127858      * Called when the user presses their mouse button down on a tool
127859      * Adds the press class ({@link #toolPressedCls})
127860      * @private
127861      */
127862     onMouseDown: function() {
127863         if (this.disabled) {
127864             return false;
127865         }
127866
127867         this.el.addCls(this.toolPressedCls);
127868     },
127869
127870     /**
127871      * Called when the user rolls over a tool
127872      * Adds the over class ({@link #toolOverCls})
127873      * @private
127874      */
127875     onMouseOver: function() {
127876         if (this.disabled) {
127877             return false;
127878         }
127879         this.el.addCls(this.toolOverCls);
127880     },
127881
127882     /**
127883      * Called when the user rolls out from a tool.
127884      * Removes the over class ({@link #toolOverCls})
127885      * @private
127886      */
127887     onMouseOut: function() {
127888         this.el.removeCls(this.toolOverCls);
127889     }
127890 });
127891 /**
127892  * @class Ext.resizer.Handle
127893  * @extends Ext.Component
127894  *
127895  * Provides a handle for 9-point resizing of Elements or Components.
127896  */
127897 Ext.define('Ext.resizer.Handle', {
127898     extend: 'Ext.Component',
127899     handleCls: '',
127900     baseHandleCls: Ext.baseCSSPrefix + 'resizable-handle',
127901     // Ext.resizer.Resizer.prototype.possiblePositions define the regions
127902     // which will be passed in as a region configuration.
127903     region: '',
127904
127905     onRender: function() {
127906         this.addCls(
127907             this.baseHandleCls,
127908             this.baseHandleCls + '-' + this.region,
127909             this.handleCls
127910         );
127911         this.callParent(arguments);
127912         this.el.unselectable();
127913     }
127914 });
127915
127916 /**
127917  * Applies drag handles to an element or component to make it resizable. The drag handles are inserted into the element
127918  * (or component's element) and positioned absolute.
127919  *
127920  * Textarea and img elements will be wrapped with an additional div because these elements do not support child nodes.
127921  * The original element can be accessed through the originalTarget property.
127922  *
127923  * Here is the list of valid resize handles:
127924  *
127925  *     Value   Description
127926  *     ------  -------------------
127927  *      'n'     north
127928  *      's'     south
127929  *      'e'     east
127930  *      'w'     west
127931  *      'nw'    northwest
127932  *      'sw'    southwest
127933  *      'se'    southeast
127934  *      'ne'    northeast
127935  *      'all'   all
127936  *
127937  * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
127938  *
127939  * Here's an example showing the creation of a typical Resizer:
127940  *
127941  *     Ext.create('Ext.resizer.Resizer', {
127942  *         el: 'elToResize',
127943  *         handles: 'all',
127944  *         minWidth: 200,
127945  *         minHeight: 100,
127946  *         maxWidth: 500,
127947  *         maxHeight: 400,
127948  *         pinned: true
127949  *     });
127950  */
127951 Ext.define('Ext.resizer.Resizer', {
127952     mixins: {
127953         observable: 'Ext.util.Observable'
127954     },
127955     uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],
127956
127957     alternateClassName: 'Ext.Resizable',
127958
127959     handleCls: Ext.baseCSSPrefix + 'resizable-handle',
127960     pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
127961     overCls:   Ext.baseCSSPrefix + 'resizable-over',
127962     wrapCls:   Ext.baseCSSPrefix + 'resizable-wrap',
127963
127964     /**
127965      * @cfg {Boolean} dynamic
127966      * Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during
127967      * dragging. This is `true` by default, but the {@link Ext.Component Component} class passes `false` when it is
127968      * configured as {@link Ext.Component#resizable}.
127969      *
127970      * If specified as `false`, a proxy element is displayed during the resize operation, and the {@link #target} is
127971      * updated on mouseup.
127972      */
127973     dynamic: true,
127974
127975     /**
127976      * @cfg {String} handles
127977      * String consisting of the resize handles to display. Defaults to 's e se' for Elements and fixed position
127978      * Components. Defaults to 8 point resizing for floating Components (such as Windows). Specify either `'all'` or any
127979      * of `'n s e w ne nw se sw'`.
127980      */
127981     handles: 's e se',
127982
127983     /**
127984      * @cfg {Number} height
127985      * Optional. The height to set target to in pixels
127986      */
127987     height : null,
127988
127989     /**
127990      * @cfg {Number} width
127991      * Optional. The width to set the target to in pixels
127992      */
127993     width : null,
127994
127995     /**
127996      * @cfg {Number} heightIncrement
127997      * The increment to snap the height resize in pixels.
127998      */
127999     heightIncrement : 0,
128000
128001     /**
128002      * @cfg {Number} widthIncrement
128003      * The increment to snap the width resize in pixels.
128004      */
128005     widthIncrement : 0,
128006
128007     /**
128008      * @cfg {Number} minHeight
128009      * The minimum height for the element
128010      */
128011     minHeight : 20,
128012
128013     /**
128014      * @cfg {Number} minWidth
128015      * The minimum width for the element
128016      */
128017     minWidth : 20,
128018
128019     /**
128020      * @cfg {Number} maxHeight
128021      * The maximum height for the element
128022      */
128023     maxHeight : 10000,
128024
128025     /**
128026      * @cfg {Number} maxWidth
128027      * The maximum width for the element
128028      */
128029     maxWidth : 10000,
128030
128031     /**
128032      * @cfg {Boolean} pinned
128033      * True to ensure that the resize handles are always visible, false indicates resizing by cursor changes only
128034      */
128035     pinned: false,
128036
128037     /**
128038      * @cfg {Boolean} preserveRatio
128039      * True to preserve the original ratio between height and width during resize
128040      */
128041     preserveRatio: false,
128042
128043     /**
128044      * @cfg {Boolean} transparent
128045      * True for transparent handles. This is only applied at config time.
128046      */
128047     transparent: false,
128048
128049     /**
128050      * @cfg {Ext.Element/Ext.util.Region} constrainTo
128051      * An element, or a {@link Ext.util.Region Region} into which the resize operation must be constrained.
128052      */
128053
128054     possiblePositions: {
128055         n:  'north',
128056         s:  'south',
128057         e:  'east',
128058         w:  'west',
128059         se: 'southeast',
128060         sw: 'southwest',
128061         nw: 'northwest',
128062         ne: 'northeast'
128063     },
128064
128065     /**
128066      * @cfg {Ext.Element/Ext.Component} target
128067      * The Element or Component to resize.
128068      */
128069
128070     /**
128071      * @property {Ext.Element} el
128072      * Outer element for resizing behavior.
128073      */
128074
128075     constructor: function(config) {
128076         var me = this,
128077             target,
128078             tag,
128079             handles = me.handles,
128080             handleCls,
128081             possibles,
128082             len,
128083             i = 0,
128084             pos;
128085
128086         this.addEvents(
128087             /**
128088              * @event beforeresize
128089              * Fired before resize is allowed. Return false to cancel resize.
128090              * @param {Ext.resizer.Resizer} this
128091              * @param {Number} width The start width
128092              * @param {Number} height The start height
128093              * @param {Ext.EventObject} e The mousedown event
128094              */
128095             'beforeresize',
128096             /**
128097              * @event resizedrag
128098              * Fires during resizing. Return false to cancel resize.
128099              * @param {Ext.resizer.Resizer} this
128100              * @param {Number} width The new width
128101              * @param {Number} height The new height
128102              * @param {Ext.EventObject} e The mousedown event
128103              */
128104             'resizedrag',
128105             /**
128106              * @event resize
128107              * Fired after a resize.
128108              * @param {Ext.resizer.Resizer} this
128109              * @param {Number} width The new width
128110              * @param {Number} height The new height
128111              * @param {Ext.EventObject} e The mouseup event
128112              */
128113             'resize'
128114         );
128115
128116         if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
128117             target = config;
128118             config = arguments[1] || {};
128119             config.target = target;
128120         }
128121         // will apply config to this
128122         me.mixins.observable.constructor.call(me, config);
128123
128124         // If target is a Component, ensure that we pull the element out.
128125         // Resizer must examine the underlying Element.
128126         target = me.target;
128127         if (target) {
128128             if (target.isComponent) {
128129                 me.el = target.getEl();
128130                 if (target.minWidth) {
128131                     me.minWidth = target.minWidth;
128132                 }
128133                 if (target.minHeight) {
128134                     me.minHeight = target.minHeight;
128135                 }
128136                 if (target.maxWidth) {
128137                     me.maxWidth = target.maxWidth;
128138                 }
128139                 if (target.maxHeight) {
128140                     me.maxHeight = target.maxHeight;
128141                 }
128142                 if (target.floating) {
128143                     if (!this.hasOwnProperty('handles')) {
128144                         this.handles = 'n ne e se s sw w nw';
128145                     }
128146                 }
128147             } else {
128148                 me.el = me.target = Ext.get(target);
128149             }
128150         }
128151         // Backwards compatibility with Ext3.x's Resizable which used el as a config.
128152         else {
128153             me.target = me.el = Ext.get(me.el);
128154         }
128155
128156         // Tags like textarea and img cannot
128157         // have children and therefore must
128158         // be wrapped
128159         tag = me.el.dom.tagName;
128160         if (tag == 'TEXTAREA' || tag == 'IMG') {
128161             /**
128162              * @property {Ext.Element/Ext.Component} originalTarget
128163              * Reference to the original resize target if the element of the original resize target was an IMG or a
128164              * TEXTAREA which must be wrapped in a DIV.
128165              */
128166             me.originalTarget = me.target;
128167             me.target = me.el = me.el.wrap({
128168                 cls: me.wrapCls,
128169                 id: me.el.id + '-rzwrap'
128170             });
128171
128172             // Transfer originalTarget's positioning/sizing
128173             me.el.setPositioning(me.originalTarget.getPositioning());
128174             me.originalTarget.clearPositioning();
128175             var box = me.originalTarget.getBox();
128176             me.el.setBox(box);
128177         }
128178
128179         // Position the element, this enables us to absolute position
128180         // the handles within this.el
128181         me.el.position();
128182         if (me.pinned) {
128183             me.el.addCls(me.pinnedCls);
128184         }
128185
128186         /**
128187          * @property {Ext.resizer.ResizeTracker} resizeTracker
128188          */
128189         me.resizeTracker = Ext.create('Ext.resizer.ResizeTracker', {
128190             disabled: me.disabled,
128191             target: me.target,
128192             constrainTo: me.constrainTo,
128193             overCls: me.overCls,
128194             throttle: me.throttle,
128195             originalTarget: me.originalTarget,
128196             delegate: '.' + me.handleCls,
128197             dynamic: me.dynamic,
128198             preserveRatio: me.preserveRatio,
128199             heightIncrement: me.heightIncrement,
128200             widthIncrement: me.widthIncrement,
128201             minHeight: me.minHeight,
128202             maxHeight: me.maxHeight,
128203             minWidth: me.minWidth,
128204             maxWidth: me.maxWidth
128205         });
128206
128207         // Relay the ResizeTracker's superclass events as our own resize events
128208         me.resizeTracker.on('mousedown', me.onBeforeResize, me);
128209         me.resizeTracker.on('drag', me.onResize, me);
128210         me.resizeTracker.on('dragend', me.onResizeEnd, me);
128211
128212         if (me.handles == 'all') {
128213             me.handles = 'n s e w ne nw se sw';
128214         }
128215
128216         handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
128217         possibles = me.possiblePositions;
128218         len = handles.length;
128219         handleCls = me.handleCls + ' ' + (this.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';
128220
128221         for(; i < len; i++){
128222             // if specified and possible, create
128223             if (handles[i] && possibles[handles[i]]) {
128224                 pos = possibles[handles[i]];
128225                 // store a reference in this.east, this.west, etc
128226
128227                 me[pos] = Ext.create('Ext.Component', {
128228                     owner: this,
128229                     region: pos,
128230                     cls: handleCls + pos,
128231                     renderTo: me.el
128232                 });
128233                 me[pos].el.unselectable();
128234                 if (me.transparent) {
128235                     me[pos].el.setOpacity(0);
128236                 }
128237             }
128238         }
128239
128240         // Constrain within configured maxima
128241         if (Ext.isNumber(me.width)) {
128242             me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
128243         }
128244         if (Ext.isNumber(me.height)) {
128245             me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
128246         }
128247
128248         // Size the element
128249         if (me.width != null || me.height != null) {
128250             if (me.originalTarget) {
128251                 me.originalTarget.setWidth(me.width);
128252                 me.originalTarget.setHeight(me.height);
128253             }
128254             me.resizeTo(me.width, me.height);
128255         }
128256
128257         me.forceHandlesHeight();
128258     },
128259
128260     disable: function() {
128261         this.resizeTracker.disable();
128262     },
128263
128264     enable: function() {
128265         this.resizeTracker.enable();
128266     },
128267
128268     /**
128269      * @private Relay the Tracker's mousedown event as beforeresize
128270      * @param tracker The Resizer
128271      * @param e The Event
128272      */
128273     onBeforeResize: function(tracker, e) {
128274         var b = this.target.getBox();
128275         return this.fireEvent('beforeresize', this, b.width, b.height, e);
128276     },
128277
128278     /**
128279      * @private Relay the Tracker's drag event as resizedrag
128280      * @param tracker The Resizer
128281      * @param e The Event
128282      */
128283     onResize: function(tracker, e) {
128284         var me = this,
128285             b = me.target.getBox();
128286         me.forceHandlesHeight();
128287         return me.fireEvent('resizedrag', me, b.width, b.height, e);
128288     },
128289
128290     /**
128291      * @private Relay the Tracker's dragend event as resize
128292      * @param tracker The Resizer
128293      * @param e The Event
128294      */
128295     onResizeEnd: function(tracker, e) {
128296         var me = this,
128297             b = me.target.getBox();
128298         me.forceHandlesHeight();
128299         return me.fireEvent('resize', me, b.width, b.height, e);
128300     },
128301
128302     /**
128303      * Perform a manual resize and fires the 'resize' event.
128304      * @param {Number} width
128305      * @param {Number} height
128306      */
128307     resizeTo : function(width, height){
128308         this.target.setSize(width, height);
128309         this.fireEvent('resize', this, width, height, null);
128310     },
128311
128312     /**
128313      * Returns the element that was configured with the el or target config property. If a component was configured with
128314      * the target property then this will return the element of this component.
128315      *
128316      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
128317      * nodes. The original element can be accessed through the originalTarget property.
128318      * @return {Ext.Element} element
128319      */
128320     getEl : function() {
128321         return this.el;
128322     },
128323
128324     /**
128325      * Returns the element or component that was configured with the target config property.
128326      *
128327      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
128328      * nodes. The original element can be accessed through the originalTarget property.
128329      * @return {Ext.Element/Ext.Component}
128330      */
128331     getTarget: function() {
128332         return this.target;
128333     },
128334
128335     destroy: function() {
128336         var h;
128337         for (var i = 0, l = this.handles.length; i < l; i++) {
128338             h = this[this.possiblePositions[this.handles[i]]];
128339             delete h.owner;
128340             Ext.destroy(h);
128341         }
128342     },
128343
128344     /**
128345      * @private
128346      * Fix IE6 handle height issue.
128347      */
128348     forceHandlesHeight : function() {
128349         var me = this,
128350             handle;
128351         if (Ext.isIE6) {
128352             handle = me.east;
128353             if (handle) {
128354                 handle.setHeight(me.el.getHeight());
128355             }
128356             handle = me.west;
128357             if (handle) {
128358                 handle.setHeight(me.el.getHeight());
128359             }
128360             me.el.repaint();
128361         }
128362     }
128363 });
128364
128365 /**
128366  * @class Ext.resizer.ResizeTracker
128367  * @extends Ext.dd.DragTracker
128368  * Private utility class for Ext.resizer.Resizer.
128369  * @private
128370  */
128371 Ext.define('Ext.resizer.ResizeTracker', {
128372     extend: 'Ext.dd.DragTracker',
128373     dynamic: true,
128374     preserveRatio: false,
128375
128376     // Default to no constraint
128377     constrainTo: null,
128378     
128379     proxyCls:  Ext.baseCSSPrefix + 'resizable-proxy',
128380
128381     constructor: function(config) {
128382         var me = this;
128383
128384         if (!config.el) {
128385             if (config.target.isComponent) {
128386                 me.el = config.target.getEl();
128387             } else {
128388                 me.el = config.target;
128389             }
128390         }
128391         this.callParent(arguments);
128392
128393         // Ensure that if we are preserving aspect ratio, the largest minimum is honoured
128394         if (me.preserveRatio && me.minWidth && me.minHeight) {
128395             var widthRatio = me.minWidth / me.el.getWidth(),
128396                 heightRatio = me.minHeight / me.el.getHeight();
128397
128398             // largest ratio of minimum:size must be preserved.
128399             // So if a 400x200 pixel image has
128400             // minWidth: 50, maxWidth: 50, the maxWidth will be 400 * (50/200)... that is 100
128401             if (heightRatio > widthRatio) {
128402                 me.minWidth = me.el.getWidth() * heightRatio;
128403             } else {
128404                 me.minHeight = me.el.getHeight() * widthRatio;
128405             }
128406         }
128407
128408         // If configured as throttled, create an instance version of resize which calls
128409         // a throttled function to perform the resize operation.
128410         if (me.throttle) {
128411             var throttledResizeFn = Ext.Function.createThrottled(function() {
128412                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
128413                 }, me.throttle);
128414
128415             me.resize = function(box, direction, atEnd) {
128416                 if (atEnd) {
128417                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
128418                 } else {
128419                     throttledResizeFn.apply(null, arguments);
128420                 }
128421             };
128422         }
128423     },
128424
128425     onBeforeStart: function(e) {
128426         // record the startBox
128427         this.startBox = this.el.getBox();
128428     },
128429
128430     /**
128431      * @private
128432      * Returns the object that will be resized on every mousemove event.
128433      * If dynamic is false, this will be a proxy, otherwise it will be our actual target.
128434      */
128435     getDynamicTarget: function() {
128436         var me = this,
128437             target = me.target;
128438             
128439         if (me.dynamic) {
128440             return target;
128441         } else if (!me.proxy) {
128442             me.proxy = me.createProxy(target);
128443         }
128444         me.proxy.show();
128445         return me.proxy;
128446     },
128447     
128448     /**
128449      * Create a proxy for this resizer
128450      * @param {Ext.Component/Ext.Element} target The target
128451      * @return {Ext.Element} A proxy element
128452      */
128453     createProxy: function(target){
128454         var proxy,
128455             cls = this.proxyCls,
128456             renderTo;
128457             
128458         if (target.isComponent) {
128459             proxy = target.getProxy().addCls(cls);
128460         } else {
128461             renderTo = Ext.getBody();
128462             if (Ext.scopeResetCSS) {
128463                 renderTo = Ext.getBody().createChild({
128464                     cls: Ext.baseCSSPrefix + 'reset'
128465                 });
128466             }
128467             proxy = target.createProxy({
128468                 tag: 'div',
128469                 cls: cls,
128470                 id: target.id + '-rzproxy'
128471             }, renderTo);
128472         }
128473         proxy.removeCls(Ext.baseCSSPrefix + 'proxy-el');
128474         return proxy;
128475     },
128476
128477     onStart: function(e) {
128478         // returns the Ext.ResizeHandle that the user started dragging
128479         this.activeResizeHandle = Ext.getCmp(this.getDragTarget().id);
128480
128481         // If we are using a proxy, ensure it is sized.
128482         if (!this.dynamic) {
128483             this.resize(this.startBox, {
128484                 horizontal: 'none',
128485                 vertical: 'none'
128486             });
128487         }
128488     },
128489
128490     onDrag: function(e) {
128491         // dynamic resizing, update dimensions during resize
128492         if (this.dynamic || this.proxy) {
128493             this.updateDimensions(e);
128494         }
128495     },
128496
128497     updateDimensions: function(e, atEnd) {
128498         var me = this,
128499             region = me.activeResizeHandle.region,
128500             offset = me.getOffset(me.constrainTo ? 'dragTarget' : null),
128501             box = me.startBox,
128502             ratio,
128503             widthAdjust = 0,
128504             heightAdjust = 0,
128505             snappedWidth,
128506             snappedHeight,
128507             adjustX = 0,
128508             adjustY = 0,
128509             dragRatio,
128510             horizDir = offset[0] < 0 ? 'right' : 'left',
128511             vertDir = offset[1] < 0 ? 'down' : 'up',
128512             oppositeCorner,
128513             axis; // 1 = x, 2 = y, 3 = x and y.
128514
128515         switch (region) {
128516             case 'south':
128517                 heightAdjust = offset[1];
128518                 axis = 2;
128519                 break;
128520             case 'north':
128521                 heightAdjust = -offset[1];
128522                 adjustY = -heightAdjust;
128523                 axis = 2;
128524                 break;
128525             case 'east':
128526                 widthAdjust = offset[0];
128527                 axis = 1;
128528                 break;
128529             case 'west':
128530                 widthAdjust = -offset[0];
128531                 adjustX = -widthAdjust;
128532                 axis = 1;
128533                 break;
128534             case 'northeast':
128535                 heightAdjust = -offset[1];
128536                 adjustY = -heightAdjust;
128537                 widthAdjust = offset[0];
128538                 oppositeCorner = [box.x, box.y + box.height];
128539                 axis = 3;
128540                 break;
128541             case 'southeast':
128542                 heightAdjust = offset[1];
128543                 widthAdjust = offset[0];
128544                 oppositeCorner = [box.x, box.y];
128545                 axis = 3;
128546                 break;
128547             case 'southwest':
128548                 widthAdjust = -offset[0];
128549                 adjustX = -widthAdjust;
128550                 heightAdjust = offset[1];
128551                 oppositeCorner = [box.x + box.width, box.y];
128552                 axis = 3;
128553                 break;
128554             case 'northwest':
128555                 heightAdjust = -offset[1];
128556                 adjustY = -heightAdjust;
128557                 widthAdjust = -offset[0];
128558                 adjustX = -widthAdjust;
128559                 oppositeCorner = [box.x + box.width, box.y + box.height];
128560                 axis = 3;
128561                 break;
128562         }
128563
128564         var newBox = {
128565             width: box.width + widthAdjust,
128566             height: box.height + heightAdjust,
128567             x: box.x + adjustX,
128568             y: box.y + adjustY
128569         };
128570
128571         // Snap value between stops according to configured increments
128572         snappedWidth = Ext.Number.snap(newBox.width, me.widthIncrement);
128573         snappedHeight = Ext.Number.snap(newBox.height, me.heightIncrement);
128574         if (snappedWidth != newBox.width || snappedHeight != newBox.height){
128575             switch (region) {
128576                 case 'northeast':
128577                     newBox.y -= snappedHeight - newBox.height;
128578                     break;
128579                 case 'north':
128580                     newBox.y -= snappedHeight - newBox.height;
128581                     break;
128582                 case 'southwest':
128583                     newBox.x -= snappedWidth - newBox.width;
128584                     break;
128585                 case 'west':
128586                     newBox.x -= snappedWidth - newBox.width;
128587                     break;
128588                 case 'northwest':
128589                     newBox.x -= snappedWidth - newBox.width;
128590                     newBox.y -= snappedHeight - newBox.height;
128591             }
128592             newBox.width = snappedWidth;
128593             newBox.height = snappedHeight;
128594         }
128595
128596         // out of bounds
128597         if (newBox.width < me.minWidth || newBox.width > me.maxWidth) {
128598             newBox.width = Ext.Number.constrain(newBox.width, me.minWidth, me.maxWidth);
128599
128600             // Re-adjust the X position if we were dragging the west side
128601             if (adjustX) {
128602                 newBox.x = box.x + (box.width - newBox.width);
128603             }
128604         } else {
128605             me.lastX = newBox.x;
128606         }
128607         if (newBox.height < me.minHeight || newBox.height > me.maxHeight) {
128608             newBox.height = Ext.Number.constrain(newBox.height, me.minHeight, me.maxHeight);
128609
128610             // Re-adjust the Y position if we were dragging the north side
128611             if (adjustY) {
128612                 newBox.y = box.y + (box.height - newBox.height);
128613             }
128614         } else {
128615             me.lastY = newBox.y;
128616         }
128617
128618         // If this is configured to preserve the aspect ratio, or they are dragging using the shift key
128619         if (me.preserveRatio || e.shiftKey) {
128620             var newHeight,
128621                 newWidth;
128622
128623             ratio = me.startBox.width / me.startBox.height;
128624
128625             // Calculate aspect ratio constrained values.
128626             newHeight = Math.min(Math.max(me.minHeight, newBox.width / ratio), me.maxHeight);
128627             newWidth = Math.min(Math.max(me.minWidth, newBox.height * ratio), me.maxWidth);
128628
128629             // X axis: width-only change, height must obey
128630             if (axis == 1) {
128631                 newBox.height = newHeight;
128632             }
128633
128634             // Y axis: height-only change, width must obey
128635             else if (axis == 2) {
128636                 newBox.width = newWidth;
128637             }
128638
128639             // Corner drag.
128640             else {
128641                 // Drag ratio is the ratio of the mouse point from the opposite corner.
128642                 // Basically what edge we are dragging, a horizontal edge or a vertical edge.
128643                 dragRatio = Math.abs(oppositeCorner[0] - this.lastXY[0]) / Math.abs(oppositeCorner[1] - this.lastXY[1]);
128644
128645                 // If drag ratio > aspect ratio then width is dominant and height must obey
128646                 if (dragRatio > ratio) {
128647                     newBox.height = newHeight;
128648                 } else {
128649                     newBox.width = newWidth;
128650                 }
128651
128652                 // Handle dragging start coordinates
128653                 if (region == 'northeast') {
128654                     newBox.y = box.y - (newBox.height - box.height);
128655                 } else if (region == 'northwest') {
128656                     newBox.y = box.y - (newBox.height - box.height);
128657                     newBox.x = box.x - (newBox.width - box.width);
128658                 } else if (region == 'southwest') {
128659                     newBox.x = box.x - (newBox.width - box.width);
128660                 }
128661             }
128662         }
128663
128664         if (heightAdjust === 0) {
128665             vertDir = 'none';
128666         }
128667         if (widthAdjust === 0) {
128668             horizDir = 'none';
128669         }
128670         me.resize(newBox, {
128671             horizontal: horizDir,
128672             vertical: vertDir
128673         }, atEnd);
128674     },
128675
128676     getResizeTarget: function(atEnd) {
128677         return atEnd ? this.target : this.getDynamicTarget();
128678     },
128679
128680     resize: function(box, direction, atEnd) {
128681         var target = this.getResizeTarget(atEnd);
128682         if (target.isComponent) {
128683             if (target.floating) {
128684                 target.setPagePosition(box.x, box.y);
128685             }
128686             target.setSize(box.width, box.height);
128687         } else {
128688             target.setBox(box);
128689             // update the originalTarget if this was wrapped.
128690             if (this.originalTarget) {
128691                 this.originalTarget.setBox(box);
128692             }
128693         }
128694     },
128695
128696     onEnd: function(e) {
128697         this.updateDimensions(e, true);
128698         if (this.proxy) {
128699             this.proxy.hide();
128700         }
128701     }
128702 });
128703
128704 /**
128705  * @class Ext.resizer.SplitterTracker
128706  * @extends Ext.dd.DragTracker
128707  * Private utility class for Ext.Splitter.
128708  * @private
128709  */
128710 Ext.define('Ext.resizer.SplitterTracker', {
128711     extend: 'Ext.dd.DragTracker',
128712     requires: ['Ext.util.Region'],
128713     enabled: true,
128714     
128715     overlayCls: Ext.baseCSSPrefix + 'resizable-overlay',
128716
128717     getPrevCmp: function() {
128718         var splitter = this.getSplitter();
128719         return splitter.previousSibling();
128720     },
128721
128722     getNextCmp: function() {
128723         var splitter = this.getSplitter();
128724         return splitter.nextSibling();
128725     },
128726
128727     // ensure the tracker is enabled, store boxes of previous and next
128728     // components and calculate the constrain region
128729     onBeforeStart: function(e) {
128730         var me = this,
128731             prevCmp = me.getPrevCmp(),
128732             nextCmp = me.getNextCmp(),
128733             collapseEl = me.getSplitter().collapseEl,
128734             overlay;
128735             
128736         if (collapseEl && (e.getTarget() === me.getSplitter().collapseEl.dom)) {
128737             return false;
128738         }
128739
128740         // SplitterTracker is disabled if any of its adjacents are collapsed.
128741         if (nextCmp.collapsed || prevCmp.collapsed) {
128742             return false;
128743         }
128744         
128745         overlay = me.overlay =  Ext.getBody().createChild({
128746             cls: me.overlayCls, 
128747             html: '&#160;'
128748         });
128749         overlay.unselectable();
128750         overlay.setSize(Ext.Element.getViewWidth(true), Ext.Element.getViewHeight(true));
128751         overlay.show();
128752         
128753         // store boxes of previous and next
128754         me.prevBox  = prevCmp.getEl().getBox();
128755         me.nextBox  = nextCmp.getEl().getBox();
128756         me.constrainTo = me.calculateConstrainRegion();
128757     },
128758
128759     // We move the splitter el. Add the proxy class.
128760     onStart: function(e) {
128761         var splitter = this.getSplitter();
128762         splitter.addCls(splitter.baseCls + '-active');
128763     },
128764
128765     // calculate the constrain Region in which the splitter el may be moved.
128766     calculateConstrainRegion: function() {
128767         var me         = this,
128768             splitter   = me.getSplitter(),
128769             splitWidth = splitter.getWidth(),
128770             defaultMin = splitter.defaultSplitMin,
128771             orient     = splitter.orientation,
128772             prevBox    = me.prevBox,
128773             prevCmp    = me.getPrevCmp(),
128774             nextBox    = me.nextBox,
128775             nextCmp    = me.getNextCmp(),
128776             // prev and nextConstrainRegions are the maximumBoxes minus the
128777             // minimumBoxes. The result is always the intersection
128778             // of these two boxes.
128779             prevConstrainRegion, nextConstrainRegion;
128780
128781         // vertical splitters, so resizing left to right
128782         if (orient === 'vertical') {
128783
128784             // Region constructor accepts (top, right, bottom, left)
128785             // anchored/calculated from the left
128786             prevConstrainRegion = Ext.create('Ext.util.Region',
128787                 prevBox.y,
128788                 // Right boundary is x + maxWidth if there IS a maxWidth.
128789                 // Otherwise it is calculated based upon the minWidth of the next Component
128790                 (prevCmp.maxWidth ? prevBox.x + prevCmp.maxWidth : nextBox.right - (nextCmp.minWidth || defaultMin)) + splitWidth,
128791                 prevBox.bottom,
128792                 prevBox.x + (prevCmp.minWidth || defaultMin)
128793             );
128794             // anchored/calculated from the right
128795             nextConstrainRegion = Ext.create('Ext.util.Region',
128796                 nextBox.y,
128797                 nextBox.right - (nextCmp.minWidth || defaultMin),
128798                 nextBox.bottom,
128799                 // Left boundary is right - maxWidth if there IS a maxWidth.
128800                 // Otherwise it is calculated based upon the minWidth of the previous Component
128801                 (nextCmp.maxWidth ? nextBox.right - nextCmp.maxWidth : prevBox.x + (prevBox.minWidth || defaultMin)) - splitWidth
128802             );
128803         } else {
128804             // anchored/calculated from the top
128805             prevConstrainRegion = Ext.create('Ext.util.Region',
128806                 prevBox.y + (prevCmp.minHeight || defaultMin),
128807                 prevBox.right,
128808                 // Bottom boundary is y + maxHeight if there IS a maxHeight.
128809                 // Otherwise it is calculated based upon the minWidth of the next Component
128810                 (prevCmp.maxHeight ? prevBox.y + prevCmp.maxHeight : nextBox.bottom - (nextCmp.minHeight || defaultMin)) + splitWidth,
128811                 prevBox.x
128812             );
128813             // anchored/calculated from the bottom
128814             nextConstrainRegion = Ext.create('Ext.util.Region',
128815                 // Top boundary is bottom - maxHeight if there IS a maxHeight.
128816                 // Otherwise it is calculated based upon the minHeight of the previous Component
128817                 (nextCmp.maxHeight ? nextBox.bottom - nextCmp.maxHeight : prevBox.y + (prevCmp.minHeight || defaultMin)) - splitWidth,
128818                 nextBox.right,
128819                 nextBox.bottom - (nextCmp.minHeight || defaultMin),
128820                 nextBox.x
128821             );
128822         }
128823
128824         // intersection of the two regions to provide region draggable
128825         return prevConstrainRegion.intersect(nextConstrainRegion);
128826     },
128827
128828     // Performs the actual resizing of the previous and next components
128829     performResize: function(e) {
128830         var me       = this,
128831             offset   = me.getOffset('dragTarget'),
128832             splitter = me.getSplitter(),
128833             orient   = splitter.orientation,
128834             prevCmp  = me.getPrevCmp(),
128835             nextCmp  = me.getNextCmp(),
128836             owner    = splitter.ownerCt,
128837             layout   = owner.getLayout();
128838
128839         // Inhibit automatic container layout caused by setSize calls below.
128840         owner.suspendLayout = true;
128841
128842         if (orient === 'vertical') {
128843             if (prevCmp) {
128844                 if (!prevCmp.maintainFlex) {
128845                     delete prevCmp.flex;
128846                     prevCmp.setSize(me.prevBox.width + offset[0], prevCmp.getHeight());
128847                 }
128848             }
128849             if (nextCmp) {
128850                 if (!nextCmp.maintainFlex) {
128851                     delete nextCmp.flex;
128852                     nextCmp.setSize(me.nextBox.width - offset[0], nextCmp.getHeight());
128853                 }
128854             }
128855         // verticals
128856         } else {
128857             if (prevCmp) {
128858                 if (!prevCmp.maintainFlex) {
128859                     delete prevCmp.flex;
128860                     prevCmp.setSize(prevCmp.getWidth(), me.prevBox.height + offset[1]);
128861                 }
128862             }
128863             if (nextCmp) {
128864                 if (!nextCmp.maintainFlex) {
128865                     delete nextCmp.flex;
128866                     nextCmp.setSize(prevCmp.getWidth(), me.nextBox.height - offset[1]);
128867                 }
128868             }
128869         }
128870         delete owner.suspendLayout;
128871         layout.onLayout();
128872     },
128873
128874     // Cleans up the overlay (if we have one) and calls the base. This cannot be done in
128875     // onEnd, because onEnd is only called if a drag is detected but the overlay is created
128876     // regardless (by onBeforeStart).
128877     endDrag: function () {
128878         var me = this;
128879
128880         if (me.overlay) {
128881              me.overlay.remove();
128882              delete me.overlay;
128883         }
128884
128885         me.callParent(arguments); // this calls onEnd
128886     },
128887
128888     // perform the resize and remove the proxy class from the splitter el
128889     onEnd: function(e) {
128890         var me = this,
128891             splitter = me.getSplitter();
128892             
128893         splitter.removeCls(splitter.baseCls + '-active');
128894         me.performResize();
128895     },
128896
128897     // Track the proxy and set the proper XY coordinates
128898     // while constraining the drag
128899     onDrag: function(e) {
128900         var me        = this,
128901             offset    = me.getOffset('dragTarget'),
128902             splitter  = me.getSplitter(),
128903             splitEl   = splitter.getEl(),
128904             orient    = splitter.orientation;
128905
128906         if (orient === "vertical") {
128907             splitEl.setX(me.startRegion.left + offset[0]);
128908         } else {
128909             splitEl.setY(me.startRegion.top + offset[1]);
128910         }
128911     },
128912
128913     getSplitter: function() {
128914         return Ext.getCmp(this.getDragCt().id);
128915     }
128916 });
128917 /**
128918  * @class Ext.selection.CellModel
128919  * @extends Ext.selection.Model
128920  */
128921 Ext.define('Ext.selection.CellModel', {
128922     extend: 'Ext.selection.Model',
128923     alias: 'selection.cellmodel',
128924     requires: ['Ext.util.KeyNav'],
128925
128926     /**
128927      * @cfg {Boolean} enableKeyNav
128928      * Turns on/off keyboard navigation within the grid.
128929      */
128930     enableKeyNav: true,
128931
128932     /**
128933      * @cfg {Boolean} preventWrap
128934      * Set this configuration to true to prevent wrapping around of selection as
128935      * a user navigates to the first or last column.
128936      */
128937     preventWrap: false,
128938
128939     constructor: function(){
128940         this.addEvents(
128941             /**
128942              * @event deselect
128943              * Fired after a cell is deselected
128944              * @param {Ext.selection.CellModel} this
128945              * @param {Ext.data.Model} record The record of the deselected cell
128946              * @param {Number} row The row index deselected
128947              * @param {Number} column The column index deselected
128948              */
128949             'deselect',
128950
128951             /**
128952              * @event select
128953              * Fired after a cell is selected
128954              * @param {Ext.selection.CellModel} this
128955              * @param {Ext.data.Model} record The record of the selected cell
128956              * @param {Number} row The row index selected
128957              * @param {Number} column The column index selected
128958              */
128959             'select'
128960         );
128961         this.callParent(arguments);
128962     },
128963
128964     bindComponent: function(view) {
128965         var me = this;
128966         me.primaryView = view;
128967         me.views = me.views || [];
128968         me.views.push(view);
128969         me.bind(view.getStore(), true);
128970
128971         view.on({
128972             cellmousedown: me.onMouseDown,
128973             refresh: me.onViewRefresh,
128974             scope: me
128975         });
128976
128977         if (me.enableKeyNav) {
128978             me.initKeyNav(view);
128979         }
128980     },
128981
128982     initKeyNav: function(view) {
128983         var me = this;
128984
128985         if (!view.rendered) {
128986             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
128987             return;
128988         }
128989
128990         view.el.set({
128991             tabIndex: -1
128992         });
128993
128994         // view.el has tabIndex -1 to allow for
128995         // keyboard events to be passed to it.
128996         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
128997             up: me.onKeyUp,
128998             down: me.onKeyDown,
128999             right: me.onKeyRight,
129000             left: me.onKeyLeft,
129001             tab: me.onKeyTab,
129002             scope: me
129003         });
129004     },
129005
129006     getHeaderCt: function() {
129007         return this.primaryView.headerCt;
129008     },
129009
129010     onKeyUp: function(e, t) {
129011         this.move('up', e);
129012     },
129013
129014     onKeyDown: function(e, t) {
129015         this.move('down', e);
129016     },
129017
129018     onKeyLeft: function(e, t) {
129019         this.move('left', e);
129020     },
129021
129022     onKeyRight: function(e, t) {
129023         this.move('right', e);
129024     },
129025
129026     move: function(dir, e) {
129027         var me = this,
129028             pos = me.primaryView.walkCells(me.getCurrentPosition(), dir, e, me.preventWrap);
129029         if (pos) {
129030             me.setCurrentPosition(pos);
129031         }
129032         return pos;
129033     },
129034
129035     /**
129036      * Returns the current position in the format {row: row, column: column}
129037      */
129038     getCurrentPosition: function() {
129039         return this.position;
129040     },
129041
129042     /**
129043      * Sets the current position
129044      * @param {Object} position The position to set.
129045      */
129046     setCurrentPosition: function(pos) {
129047         var me = this;
129048
129049         if (me.position) {
129050             me.onCellDeselect(me.position);
129051         }
129052         if (pos) {
129053             me.onCellSelect(pos);
129054         }
129055         me.position = pos;
129056     },
129057
129058     /**
129059      * Set the current position based on where the user clicks.
129060      * @private
129061      */
129062     onMouseDown: function(view, cell, cellIndex, record, row, rowIndex, e) {
129063         this.setCurrentPosition({
129064             row: rowIndex,
129065             column: cellIndex
129066         });
129067     },
129068
129069     // notify the view that the cell has been selected to update the ui
129070     // appropriately and bring the cell into focus
129071     onCellSelect: function(position) {
129072         var me = this,
129073             store = me.view.getStore(),
129074             record = store.getAt(position.row);
129075
129076         me.doSelect(record);
129077         me.primaryView.onCellSelect(position);
129078         // TODO: Remove temporary cellFocus call here.
129079         me.primaryView.onCellFocus(position);
129080         me.fireEvent('select', me, record, position.row, position.column);
129081     },
129082
129083     // notify view that the cell has been deselected to update the ui
129084     // appropriately
129085     onCellDeselect: function(position) {
129086         var me = this,
129087             store = me.view.getStore(),
129088             record = store.getAt(position.row);
129089
129090         me.doDeselect(record);
129091         me.primaryView.onCellDeselect(position);
129092         me.fireEvent('deselect', me, record, position.row, position.column);
129093     },
129094
129095     onKeyTab: function(e, t) {
129096         var me = this,
129097             direction = e.shiftKey ? 'left' : 'right',
129098             editingPlugin = me.view.editingPlugin,
129099             position = me.move(direction, e);
129100
129101         if (editingPlugin && position && me.wasEditing) {
129102             editingPlugin.startEditByPosition(position);
129103         }
129104         delete me.wasEditing;
129105     },
129106
129107     onEditorTab: function(editingPlugin, e) {
129108         var me = this,
129109             direction = e.shiftKey ? 'left' : 'right',
129110             position  = me.move(direction, e);
129111
129112         if (position) {
129113             editingPlugin.startEditByPosition(position);
129114             me.wasEditing = true;
129115         }
129116     },
129117
129118     refresh: function() {
129119         var pos = this.getCurrentPosition();
129120         if (pos) {
129121             this.onCellSelect(pos);
129122         }
129123     },
129124
129125     onViewRefresh: function() {
129126         var pos = this.getCurrentPosition();
129127         if (pos) {
129128             this.onCellDeselect(pos);
129129             this.setCurrentPosition(null);
129130         }
129131     },
129132
129133     selectByPosition: function(position) {
129134         this.setCurrentPosition(position);
129135     }
129136 });
129137 /**
129138  * @class Ext.selection.RowModel
129139  * @extends Ext.selection.Model
129140  */
129141 Ext.define('Ext.selection.RowModel', {
129142     extend: 'Ext.selection.Model',
129143     alias: 'selection.rowmodel',
129144     requires: ['Ext.util.KeyNav'],
129145
129146     /**
129147      * @private
129148      * Number of pixels to scroll to the left/right when pressing
129149      * left/right keys.
129150      */
129151     deltaScroll: 5,
129152
129153     /**
129154      * @cfg {Boolean} enableKeyNav
129155      *
129156      * Turns on/off keyboard navigation within the grid.
129157      */
129158     enableKeyNav: true,
129159     
129160     /**
129161      * @cfg {Boolean} [ignoreRightMouseSelection=true]
129162      * True to ignore selections that are made when using the right mouse button if there are
129163      * records that are already selected. If no records are selected, selection will continue 
129164      * as normal
129165      */
129166     ignoreRightMouseSelection: true,
129167
129168     constructor: function(){
129169         this.addEvents(
129170             /**
129171              * @event beforedeselect
129172              * Fired before a record is deselected. If any listener returns false, the
129173              * deselection is cancelled.
129174              * @param {Ext.selection.RowModel} this
129175              * @param {Ext.data.Model} record The deselected record
129176              * @param {Number} index The row index deselected
129177              */
129178             'beforedeselect',
129179
129180             /**
129181              * @event beforeselect
129182              * Fired before a record is selected. If any listener returns false, the
129183              * selection is cancelled.
129184              * @param {Ext.selection.RowModel} this
129185              * @param {Ext.data.Model} record The selected record
129186              * @param {Number} index The row index selected
129187              */
129188             'beforeselect',
129189
129190             /**
129191              * @event deselect
129192              * Fired after a record is deselected
129193              * @param {Ext.selection.RowModel} this
129194              * @param {Ext.data.Model} record The deselected record
129195              * @param {Number} index The row index deselected
129196              */
129197             'deselect',
129198
129199             /**
129200              * @event select
129201              * Fired after a record is selected
129202              * @param {Ext.selection.RowModel} this
129203              * @param {Ext.data.Model} record The selected record
129204              * @param {Number} index The row index selected
129205              */
129206             'select'
129207         );
129208         this.callParent(arguments);
129209     },
129210
129211     bindComponent: function(view) {
129212         var me = this;
129213
129214         me.views = me.views || [];
129215         me.views.push(view);
129216         me.bind(view.getStore(), true);
129217
129218         view.on({
129219             itemmousedown: me.onRowMouseDown,
129220             scope: me
129221         });
129222
129223         if (me.enableKeyNav) {
129224             me.initKeyNav(view);
129225         }
129226     },
129227
129228     initKeyNav: function(view) {
129229         var me = this;
129230
129231         if (!view.rendered) {
129232             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
129233             return;
129234         }
129235
129236         view.el.set({
129237             tabIndex: -1
129238         });
129239
129240         // view.el has tabIndex -1 to allow for
129241         // keyboard events to be passed to it.
129242         me.keyNav = new Ext.util.KeyNav(view.el, {
129243             up: me.onKeyUp,
129244             down: me.onKeyDown,
129245             right: me.onKeyRight,
129246             left: me.onKeyLeft,
129247             pageDown: me.onKeyPageDown,
129248             pageUp: me.onKeyPageUp,
129249             home: me.onKeyHome,
129250             end: me.onKeyEnd,
129251             scope: me
129252         });
129253         view.el.on(Ext.EventManager.getKeyEvent(), me.onKeyPress, me);
129254     },
129255
129256     // Returns the number of rows currently visible on the screen or
129257     // false if there were no rows. This assumes that all rows are
129258     // of the same height and the first view is accurate.
129259     getRowsVisible: function() {
129260         var rowsVisible = false,
129261             view = this.views[0],
129262             row = view.getNode(0),
129263             rowHeight, gridViewHeight;
129264
129265         if (row) {
129266             rowHeight = Ext.fly(row).getHeight();
129267             gridViewHeight = view.el.getHeight();
129268             rowsVisible = Math.floor(gridViewHeight / rowHeight);
129269         }
129270
129271         return rowsVisible;
129272     },
129273
129274     // go to last visible record in grid.
129275     onKeyEnd: function(e, t) {
129276         var me = this,
129277             last = me.store.getAt(me.store.getCount() - 1);
129278
129279         if (last) {
129280             if (e.shiftKey) {
129281                 me.selectRange(last, me.lastFocused || 0);
129282                 me.setLastFocused(last);
129283             } else if (e.ctrlKey) {
129284                 me.setLastFocused(last);
129285             } else {
129286                 me.doSelect(last);
129287             }
129288         }
129289     },
129290
129291     // go to first visible record in grid.
129292     onKeyHome: function(e, t) {
129293         var me = this,
129294             first = me.store.getAt(0);
129295
129296         if (first) {
129297             if (e.shiftKey) {
129298                 me.selectRange(first, me.lastFocused || 0);
129299                 me.setLastFocused(first);
129300             } else if (e.ctrlKey) {
129301                 me.setLastFocused(first);
129302             } else {
129303                 me.doSelect(first, false);
129304             }
129305         }
129306     },
129307
129308     // Go one page up from the lastFocused record in the grid.
129309     onKeyPageUp: function(e, t) {
129310         var me = this,
129311             rowsVisible = me.getRowsVisible(),
129312             selIdx,
129313             prevIdx,
129314             prevRecord,
129315             currRec;
129316
129317         if (rowsVisible) {
129318             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
129319             prevIdx = selIdx - rowsVisible;
129320             if (prevIdx < 0) {
129321                 prevIdx = 0;
129322             }
129323             prevRecord = me.store.getAt(prevIdx);
129324             if (e.shiftKey) {
129325                 currRec = me.store.getAt(selIdx);
129326                 me.selectRange(prevRecord, currRec, e.ctrlKey, 'up');
129327                 me.setLastFocused(prevRecord);
129328             } else if (e.ctrlKey) {
129329                 e.preventDefault();
129330                 me.setLastFocused(prevRecord);
129331             } else {
129332                 me.doSelect(prevRecord);
129333             }
129334
129335         }
129336     },
129337
129338     // Go one page down from the lastFocused record in the grid.
129339     onKeyPageDown: function(e, t) {
129340         var me = this,
129341             rowsVisible = me.getRowsVisible(),
129342             selIdx,
129343             nextIdx,
129344             nextRecord,
129345             currRec;
129346
129347         if (rowsVisible) {
129348             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
129349             nextIdx = selIdx + rowsVisible;
129350             if (nextIdx >= me.store.getCount()) {
129351                 nextIdx = me.store.getCount() - 1;
129352             }
129353             nextRecord = me.store.getAt(nextIdx);
129354             if (e.shiftKey) {
129355                 currRec = me.store.getAt(selIdx);
129356                 me.selectRange(nextRecord, currRec, e.ctrlKey, 'down');
129357                 me.setLastFocused(nextRecord);
129358             } else if (e.ctrlKey) {
129359                 // some browsers, this means go thru browser tabs
129360                 // attempt to stop.
129361                 e.preventDefault();
129362                 me.setLastFocused(nextRecord);
129363             } else {
129364                 me.doSelect(nextRecord);
129365             }
129366         }
129367     },
129368
129369     // Select/Deselect based on pressing Spacebar.
129370     // Assumes a SIMPLE selectionmode style
129371     onKeyPress: function(e, t) {
129372         if (e.getKey() === e.SPACE) {
129373             e.stopEvent();
129374             var me = this,
129375                 record = me.lastFocused;
129376
129377             if (record) {
129378                 if (me.isSelected(record)) {
129379                     me.doDeselect(record, false);
129380                 } else {
129381                     me.doSelect(record, true);
129382                 }
129383             }
129384         }
129385     },
129386
129387     // Navigate one record up. This could be a selection or
129388     // could be simply focusing a record for discontiguous
129389     // selection. Provides bounds checking.
129390     onKeyUp: function(e, t) {
129391         var me = this,
129392             view = me.views[0],
129393             idx  = me.store.indexOf(me.lastFocused),
129394             record;
129395
129396         if (idx > 0) {
129397             // needs to be the filtered count as thats what
129398             // will be visible.
129399             record = me.store.getAt(idx - 1);
129400             if (e.shiftKey && me.lastFocused) {
129401                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
129402                     me.doDeselect(me.lastFocused, true);
129403                     me.setLastFocused(record);
129404                 } else if (!me.isSelected(me.lastFocused)) {
129405                     me.doSelect(me.lastFocused, true);
129406                     me.doSelect(record, true);
129407                 } else {
129408                     me.doSelect(record, true);
129409                 }
129410             } else if (e.ctrlKey) {
129411                 me.setLastFocused(record);
129412             } else {
129413                 me.doSelect(record);
129414                 //view.focusRow(idx - 1);
129415             }
129416         }
129417         // There was no lastFocused record, and the user has pressed up
129418         // Ignore??
129419         //else if (this.selected.getCount() == 0) {
129420         //
129421         //    this.doSelect(record);
129422         //    //view.focusRow(idx - 1);
129423         //}
129424     },
129425
129426     // Navigate one record down. This could be a selection or
129427     // could be simply focusing a record for discontiguous
129428     // selection. Provides bounds checking.
129429     onKeyDown: function(e, t) {
129430         var me = this,
129431             view = me.views[0],
129432             idx  = me.store.indexOf(me.lastFocused),
129433             record;
129434
129435         // needs to be the filtered count as thats what
129436         // will be visible.
129437         if (idx + 1 < me.store.getCount()) {
129438             record = me.store.getAt(idx + 1);
129439             if (me.selected.getCount() === 0) {
129440                 me.doSelect(record);
129441                 //view.focusRow(idx + 1);
129442             } else if (e.shiftKey && me.lastFocused) {
129443                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
129444                     me.doDeselect(me.lastFocused, true);
129445                     me.setLastFocused(record);
129446                 } else if (!me.isSelected(me.lastFocused)) {
129447                     me.doSelect(me.lastFocused, true);
129448                     me.doSelect(record, true);
129449                 } else {
129450                     me.doSelect(record, true);
129451                 }
129452             } else if (e.ctrlKey) {
129453                 me.setLastFocused(record);
129454             } else {
129455                 me.doSelect(record);
129456                 //view.focusRow(idx + 1);
129457             }
129458         }
129459     },
129460
129461     scrollByDeltaX: function(delta) {
129462         var view    = this.views[0],
129463             section = view.up(),
129464             hScroll = section.horizontalScroller;
129465
129466         if (hScroll) {
129467             hScroll.scrollByDeltaX(delta);
129468         }
129469     },
129470
129471     onKeyLeft: function(e, t) {
129472         this.scrollByDeltaX(-this.deltaScroll);
129473     },
129474
129475     onKeyRight: function(e, t) {
129476         this.scrollByDeltaX(this.deltaScroll);
129477     },
129478
129479     // Select the record with the event included so that
129480     // we can take into account ctrlKey, shiftKey, etc
129481     onRowMouseDown: function(view, record, item, index, e) {
129482         view.el.focus();
129483         if (!this.allowRightMouseSelection(e)) {
129484             return;
129485         }
129486         this.selectWithEvent(record, e);
129487     },
129488     
129489     /**
129490      * Checks whether a selection should proceed based on the ignoreRightMouseSelection
129491      * option.
129492      * @private
129493      * @param {Ext.EventObject} e The event
129494      * @return {Boolean} False if the selection should not proceed
129495      */
129496     allowRightMouseSelection: function(e) {
129497         var disallow = this.ignoreRightMouseSelection && e.button !== 0;
129498         if (disallow) {
129499             disallow = this.hasSelection();
129500         }
129501         return !disallow;
129502     },
129503
129504     // Allow the GridView to update the UI by
129505     // adding/removing a CSS class from the row.
129506     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
129507         var me      = this,
129508             views   = me.views,
129509             viewsLn = views.length,
129510             store   = me.store,
129511             rowIdx  = store.indexOf(record),
129512             eventName = isSelected ? 'select' : 'deselect',
129513             i = 0;
129514
129515         if ((suppressEvent || me.fireEvent('before' + eventName, me, record, rowIdx)) !== false &&
129516                 commitFn() !== false) {
129517
129518             for (; i < viewsLn; i++) {
129519                 if (isSelected) {
129520                     views[i].onRowSelect(rowIdx, suppressEvent);
129521                 } else {
129522                     views[i].onRowDeselect(rowIdx, suppressEvent);
129523                 }
129524             }
129525
129526             if (!suppressEvent) {
129527                 me.fireEvent(eventName, me, record, rowIdx);
129528             }
129529         }
129530     },
129531
129532     // Provide indication of what row was last focused via
129533     // the gridview.
129534     onLastFocusChanged: function(oldFocused, newFocused, supressFocus) {
129535         var views   = this.views,
129536             viewsLn = views.length,
129537             store   = this.store,
129538             rowIdx,
129539             i = 0;
129540
129541         if (oldFocused) {
129542             rowIdx = store.indexOf(oldFocused);
129543             if (rowIdx != -1) {
129544                 for (; i < viewsLn; i++) {
129545                     views[i].onRowFocus(rowIdx, false);
129546                 }
129547             }
129548         }
129549
129550         if (newFocused) {
129551             rowIdx = store.indexOf(newFocused);
129552             if (rowIdx != -1) {
129553                 for (i = 0; i < viewsLn; i++) {
129554                     views[i].onRowFocus(rowIdx, true, supressFocus);
129555                 }
129556             }
129557         }
129558     },
129559
129560     onEditorTab: function(editingPlugin, e) {
129561         var me = this,
129562             view = me.views[0],
129563             record = editingPlugin.getActiveRecord(),
129564             header = editingPlugin.getActiveColumn(),
129565             position = view.getPosition(record, header),
129566             direction = e.shiftKey ? 'left' : 'right',
129567             newPosition  = view.walkCells(position, direction, e, this.preventWrap);
129568
129569         if (newPosition) {
129570             editingPlugin.startEditByPosition(newPosition);
129571         }
129572     },
129573
129574     selectByPosition: function(position) {
129575         var record = this.store.getAt(position.row);
129576         this.select(record);
129577     }
129578 });
129579 /**
129580  * @class Ext.selection.CheckboxModel
129581  * @extends Ext.selection.RowModel
129582  *
129583  * A selection model that renders a column of checkboxes that can be toggled to
129584  * select or deselect rows. The default mode for this selection model is MULTI.
129585  *
129586  * The selection model will inject a header for the checkboxes in the first view
129587  * and according to the 'injectCheckbox' configuration.
129588  */
129589 Ext.define('Ext.selection.CheckboxModel', {
129590     alias: 'selection.checkboxmodel',
129591     extend: 'Ext.selection.RowModel',
129592
129593     /**
129594      * @cfg {String} mode
129595      * Modes of selection.
129596      * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'MULTI'
129597      */
129598     mode: 'MULTI',
129599
129600     /**
129601      * @cfg {Number/Boolean/String} injectCheckbox
129602      * Instructs the SelectionModel whether or not to inject the checkbox header
129603      * automatically or not. (Note: By not placing the checkbox in manually, the
129604      * grid view will need to be rendered 2x on initial render.)
129605      * Supported values are a Number index, false and the strings 'first' and 'last'.
129606      */
129607     injectCheckbox: 0,
129608
129609     /**
129610      * @cfg {Boolean} checkOnly <tt>true</tt> if rows can only be selected by clicking on the
129611      * checkbox column.
129612      */
129613     checkOnly: false,
129614
129615     headerWidth: 24,
129616
129617     // private
129618     checkerOnCls: Ext.baseCSSPrefix + 'grid-hd-checker-on',
129619
129620     bindComponent: function(view) {
129621         var me = this;
129622
129623         me.sortable = false;
129624         me.callParent(arguments);
129625         if (!me.hasLockedHeader() || view.headerCt.lockedCt) {
129626             // if we have a locked header, only hook up to the first
129627             view.headerCt.on('headerclick', me.onHeaderClick, me);
129628             me.addCheckbox(true);
129629             me.mon(view.ownerCt, 'reconfigure', me.addCheckbox, me);
129630         }
129631     },
129632
129633     hasLockedHeader: function(){
129634         var hasLocked = false;
129635         Ext.each(this.views, function(view){
129636             if (view.headerCt.lockedCt) {
129637                 hasLocked = true;
129638                 return false;
129639             }
129640         });
129641         return hasLocked;
129642     },
129643
129644     /**
129645      * Add the header checkbox to the header row
129646      * @private
129647      * @param {Boolean} initial True if we're binding for the first time.
129648      */
129649     addCheckbox: function(initial){
129650         var me = this,
129651             checkbox = me.injectCheckbox,
129652             view = me.views[0],
129653             headerCt = view.headerCt;
129654
129655         if (checkbox !== false) {
129656             if (checkbox == 'first') {
129657                 checkbox = 0;
129658             } else if (checkbox == 'last') {
129659                 checkbox = headerCt.getColumnCount();
129660             }
129661             headerCt.add(checkbox,  me.getHeaderConfig());
129662         }
129663
129664         if (initial !== true) {
129665             view.refresh();
129666         }
129667     },
129668
129669     /**
129670      * Toggle the ui header between checked and unchecked state.
129671      * @param {Boolean} isChecked
129672      * @private
129673      */
129674     toggleUiHeader: function(isChecked) {
129675         var view     = this.views[0],
129676             headerCt = view.headerCt,
129677             checkHd  = headerCt.child('gridcolumn[isCheckerHd]');
129678
129679         if (checkHd) {
129680             if (isChecked) {
129681                 checkHd.el.addCls(this.checkerOnCls);
129682             } else {
129683                 checkHd.el.removeCls(this.checkerOnCls);
129684             }
129685         }
129686     },
129687
129688     /**
129689      * Toggle between selecting all and deselecting all when clicking on
129690      * a checkbox header.
129691      */
129692     onHeaderClick: function(headerCt, header, e) {
129693         if (header.isCheckerHd) {
129694             e.stopEvent();
129695             var isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
129696             if (isChecked) {
129697                 // We have to supress the event or it will scrollTo the change
129698                 this.deselectAll(true);
129699             } else {
129700                 // We have to supress the event or it will scrollTo the change
129701                 this.selectAll(true);
129702             }
129703         }
129704     },
129705
129706     /**
129707      * Retrieve a configuration to be used in a HeaderContainer.
129708      * This should be used when injectCheckbox is set to false.
129709      */
129710     getHeaderConfig: function() {
129711         var me = this;
129712
129713         return {
129714             isCheckerHd: true,
129715             text : '&#160;',
129716             width: me.headerWidth,
129717             sortable: false,
129718             draggable: false,
129719             resizable: false,
129720             hideable: false,
129721             menuDisabled: true,
129722             dataIndex: '',
129723             cls: Ext.baseCSSPrefix + 'column-header-checkbox ',
129724             renderer: Ext.Function.bind(me.renderer, me),
129725             locked: me.hasLockedHeader()
129726         };
129727     },
129728
129729     /**
129730      * Generates the HTML to be rendered in the injected checkbox column for each row.
129731      * Creates the standard checkbox markup by default; can be overridden to provide custom rendering.
129732      * See {@link Ext.grid.column.Column#renderer} for description of allowed parameters.
129733      */
129734     renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
129735         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
129736         return '<div class="' + Ext.baseCSSPrefix + 'grid-row-checker">&#160;</div>';
129737     },
129738
129739     // override
129740     onRowMouseDown: function(view, record, item, index, e) {
129741         view.el.focus();
129742         var me = this,
129743             checker = e.getTarget('.' + Ext.baseCSSPrefix + 'grid-row-checker');
129744             
129745         if (!me.allowRightMouseSelection(e)) {
129746             return;
129747         }
129748
129749         // checkOnly set, but we didn't click on a checker.
129750         if (me.checkOnly && !checker) {
129751             return;
129752         }
129753
129754         if (checker) {
129755             var mode = me.getSelectionMode();
129756             // dont change the mode if its single otherwise
129757             // we would get multiple selection
129758             if (mode !== 'SINGLE') {
129759                 me.setSelectionMode('SIMPLE');
129760             }
129761             me.selectWithEvent(record, e);
129762             me.setSelectionMode(mode);
129763         } else {
129764             me.selectWithEvent(record, e);
129765         }
129766     },
129767
129768     /**
129769      * Synchronize header checker value as selection changes.
129770      * @private
129771      */
129772     onSelectChange: function() {
129773         this.callParent(arguments);
129774
129775         // check to see if all records are selected
129776         var hdSelectStatus = this.selected.getCount() === this.store.getCount();
129777         this.toggleUiHeader(hdSelectStatus);
129778     }
129779 });
129780
129781 /**
129782  * @class Ext.selection.TreeModel
129783  * @extends Ext.selection.RowModel
129784  *
129785  * Adds custom behavior for left/right keyboard navigation for use with a tree.
129786  * Depends on the view having an expand and collapse method which accepts a
129787  * record.
129788  * 
129789  * @private
129790  */
129791 Ext.define('Ext.selection.TreeModel', {
129792     extend: 'Ext.selection.RowModel',
129793     alias: 'selection.treemodel',
129794     
129795     // typically selection models prune records from the selection
129796     // model when they are removed, because the TreeView constantly
129797     // adds/removes records as they are expanded/collapsed
129798     pruneRemoved: false,
129799     
129800     onKeyRight: function(e, t) {
129801         var focused = this.getLastFocused(),
129802             view    = this.view;
129803             
129804         if (focused) {
129805             // tree node is already expanded, go down instead
129806             // this handles both the case where we navigate to firstChild and if
129807             // there are no children to the nextSibling
129808             if (focused.isExpanded()) {
129809                 this.onKeyDown(e, t);
129810             // if its not a leaf node, expand it
129811             } else if (!focused.isLeaf()) {
129812                 view.expand(focused);
129813             }
129814         }
129815     },
129816     
129817     onKeyLeft: function(e, t) {
129818         var focused = this.getLastFocused(),
129819             view    = this.view,
129820             viewSm  = view.getSelectionModel(),
129821             parentNode, parentRecord;
129822
129823         if (focused) {
129824             parentNode = focused.parentNode;
129825             // if focused node is already expanded, collapse it
129826             if (focused.isExpanded()) {
129827                 view.collapse(focused);
129828             // has a parentNode and its not root
129829             // TODO: this needs to cover the case where the root isVisible
129830             } else if (parentNode && !parentNode.isRoot()) {
129831                 // Select a range of records when doing multiple selection.
129832                 if (e.shiftKey) {
129833                     viewSm.selectRange(parentNode, focused, e.ctrlKey, 'up');
129834                     viewSm.setLastFocused(parentNode);
129835                 // just move focus, not selection
129836                 } else if (e.ctrlKey) {
129837                     viewSm.setLastFocused(parentNode);
129838                 // select it
129839                 } else {
129840                     viewSm.select(parentNode);
129841                 }
129842             }
129843         }
129844     },
129845     
129846     onKeyPress: function(e, t) {
129847         var key = e.getKey(),
129848             selected, 
129849             checked;
129850         
129851         if (key === e.SPACE || key === e.ENTER) {
129852             e.stopEvent();
129853             selected = this.getLastSelected();
129854             if (selected) {
129855                 this.view.onCheckChange(selected);
129856             }
129857         } else {
129858             this.callParent(arguments);
129859         }
129860     }
129861 });
129862
129863 /**
129864  * @class Ext.slider.Thumb
129865  * @extends Ext.Base
129866  * @private
129867  * Represents a single thumb element on a Slider. This would not usually be created manually and would instead
129868  * be created internally by an {@link Ext.slider.Multi Multi slider}.
129869  */
129870 Ext.define('Ext.slider.Thumb', {
129871     requires: ['Ext.dd.DragTracker', 'Ext.util.Format'],
129872     /**
129873      * @private
129874      * @property {Number} topThumbZIndex
129875      * The number used internally to set the z index of the top thumb (see promoteThumb for details)
129876      */
129877     topZIndex: 10000,
129878
129879     /**
129880      * @cfg {Ext.slider.MultiSlider} slider (required)
129881      * The Slider to render to.
129882      */
129883
129884     /**
129885      * Creates new slider thumb.
129886      * @param {Object} config (optional) Config object.
129887      */
129888     constructor: function(config) {
129889         var me = this;
129890
129891         /**
129892          * @property {Ext.slider.MultiSlider} slider
129893          * The slider this thumb is contained within
129894          */
129895         Ext.apply(me, config || {}, {
129896             cls: Ext.baseCSSPrefix + 'slider-thumb',
129897
129898             /**
129899              * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
129900              */
129901             constrain: false
129902         });
129903         me.callParent([config]);
129904
129905         if (me.slider.vertical) {
129906             Ext.apply(me, Ext.slider.Thumb.Vertical);
129907         }
129908     },
129909
129910     /**
129911      * Renders the thumb into a slider
129912      */
129913     render: function() {
129914         var me = this;
129915
129916         me.el = me.slider.innerEl.insertFirst({cls: me.cls});
129917         if (me.disabled) {
129918             me.disable();
129919         }
129920         me.initEvents();
129921     },
129922
129923     /**
129924      * @private
129925      * move the thumb
129926      */
129927     move: function(v, animate){
129928         if(!animate){
129929             this.el.setLeft(v);
129930         }else{
129931             Ext.create('Ext.fx.Anim', {
129932                 target: this.el,
129933                 duration: 350,
129934                 to: {
129935                     left: v
129936                 }
129937             });
129938         }
129939     },
129940
129941     /**
129942      * @private
129943      * Bring thumb dom element to front.
129944      */
129945     bringToFront: function() {
129946         this.el.setStyle('zIndex', this.topZIndex);
129947     },
129948
129949     /**
129950      * @private
129951      * Send thumb dom element to back.
129952      */
129953     sendToBack: function() {
129954         this.el.setStyle('zIndex', '');
129955     },
129956
129957     /**
129958      * Enables the thumb if it is currently disabled
129959      */
129960     enable: function() {
129961         var me = this;
129962
129963         me.disabled = false;
129964         if (me.el) {
129965             me.el.removeCls(me.slider.disabledCls);
129966         }
129967     },
129968
129969     /**
129970      * Disables the thumb if it is currently enabled
129971      */
129972     disable: function() {
129973         var me = this;
129974
129975         me.disabled = true;
129976         if (me.el) {
129977             me.el.addCls(me.slider.disabledCls);
129978         }
129979     },
129980
129981     /**
129982      * Sets up an Ext.dd.DragTracker for this thumb
129983      */
129984     initEvents: function() {
129985         var me = this,
129986             el = me.el;
129987
129988         me.tracker = Ext.create('Ext.dd.DragTracker', {
129989             onBeforeStart: Ext.Function.bind(me.onBeforeDragStart, me),
129990             onStart      : Ext.Function.bind(me.onDragStart, me),
129991             onDrag       : Ext.Function.bind(me.onDrag, me),
129992             onEnd        : Ext.Function.bind(me.onDragEnd, me),
129993             tolerance    : 3,
129994             autoStart    : 300,
129995             overCls      : Ext.baseCSSPrefix + 'slider-thumb-over'
129996         });
129997
129998         me.tracker.initEl(el);
129999     },
130000
130001     /**
130002      * @private
130003      * This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled,
130004      * this returns false to disable the DragTracker too.
130005      * @return {Boolean} False if the slider is currently disabled
130006      */
130007     onBeforeDragStart : function(e) {
130008         if (this.disabled) {
130009             return false;
130010         } else {
130011             this.slider.promoteThumb(this);
130012             return true;
130013         }
130014     },
130015
130016     /**
130017      * @private
130018      * This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class
130019      * to the thumb and fires the 'dragstart' event
130020      */
130021     onDragStart: function(e){
130022         var me = this;
130023
130024         me.el.addCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
130025         me.dragging = true;
130026         me.dragStartValue = me.value;
130027
130028         me.slider.fireEvent('dragstart', me.slider, e, me);
130029     },
130030
130031     /**
130032      * @private
130033      * This is tied into the internal Ext.dd.DragTracker's onDrag template method. This is called every time
130034      * the DragTracker detects a drag movement. It updates the Slider's value using the position of the drag
130035      */
130036     onDrag: function(e) {
130037         var me       = this,
130038             slider   = me.slider,
130039             index    = me.index,
130040             newValue = me.getNewValue(),
130041             above,
130042             below;
130043
130044         if (me.constrain) {
130045             above = slider.thumbs[index + 1];
130046             below = slider.thumbs[index - 1];
130047
130048             if (below !== undefined && newValue <= below.value) {
130049                 newValue = below.value;
130050             }
130051
130052             if (above !== undefined && newValue >= above.value) {
130053                 newValue = above.value;
130054             }
130055         }
130056
130057         slider.setValue(index, newValue, false);
130058         slider.fireEvent('drag', slider, e, me);
130059     },
130060
130061     getNewValue: function() {
130062         var slider = this.slider,
130063             pos = slider.innerEl.translatePoints(this.tracker.getXY());
130064
130065         return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
130066     },
130067
130068     /**
130069      * @private
130070      * This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and
130071      * fires the 'changecomplete' event with the new value
130072      */
130073     onDragEnd: function(e) {
130074         var me     = this,
130075             slider = me.slider,
130076             value  = me.value;
130077
130078         me.el.removeCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
130079
130080         me.dragging = false;
130081         slider.fireEvent('dragend', slider, e);
130082
130083         if (me.dragStartValue != value) {
130084             slider.fireEvent('changecomplete', slider, value, me);
130085         }
130086     },
130087
130088     destroy: function() {
130089         Ext.destroy(this.tracker);
130090     },
130091     statics: {
130092         // Method overrides to support vertical dragging of thumb within slider
130093         Vertical: {
130094             getNewValue: function() {
130095                 var slider   = this.slider,
130096                     innerEl  = slider.innerEl,
130097                     pos      = innerEl.translatePoints(this.tracker.getXY()),
130098                     bottom   = innerEl.getHeight() - pos.top;
130099
130100                 return Ext.util.Format.round(slider.reverseValue(bottom), slider.decimalPrecision);
130101             },
130102             move: function(v, animate) {
130103                 if (!animate) {
130104                     this.el.setBottom(v);
130105                 } else {
130106                     Ext.create('Ext.fx.Anim', {
130107                         target: this.el,
130108                         duration: 350,
130109                         to: {
130110                             bottom: v
130111                         }
130112                     });
130113                 }
130114             }
130115         }
130116     }
130117 });
130118
130119 /**
130120  * Simple plugin for using an Ext.tip.Tip with a slider to show the slider value. In general this class is not created
130121  * directly, instead pass the {@link Ext.slider.Multi#useTips} and {@link Ext.slider.Multi#tipText} configuration
130122  * options to the slider directly.
130123  *
130124  *     @example
130125  *     Ext.create('Ext.slider.Single', {
130126  *         width: 214,
130127  *         minValue: 0,
130128  *         maxValue: 100,
130129  *         useTips: true,
130130  *         renderTo: Ext.getBody()
130131  *     });
130132  *
130133  * Optionally provide your own tip text by passing tipText:
130134  *
130135  *     @example
130136  *     Ext.create('Ext.slider.Single', {
130137  *         width: 214,
130138  *         minValue: 0,
130139  *         maxValue: 100,
130140  *         useTips: true,
130141  *         tipText: function(thumb){
130142  *             return Ext.String.format('**{0}% complete**', thumb.value);
130143  *         },
130144  *         renderTo: Ext.getBody()
130145  *     });
130146  */
130147 Ext.define('Ext.slider.Tip', {
130148     extend: 'Ext.tip.Tip',
130149     minWidth: 10,
130150     alias: 'widget.slidertip',
130151     offsets : [0, -10],
130152
130153     isSliderTip: true,
130154
130155     init: function(slider) {
130156         var me = this;
130157
130158         slider.on({
130159             scope    : me,
130160             dragstart: me.onSlide,
130161             drag     : me.onSlide,
130162             dragend  : me.hide,
130163             destroy  : me.destroy
130164         });
130165     },
130166     /**
130167      * @private
130168      * Called whenever a dragstart or drag event is received on the associated Thumb.
130169      * Aligns the Tip with the Thumb's new position.
130170      * @param {Ext.slider.MultiSlider} slider The slider
130171      * @param {Ext.EventObject} e The Event object
130172      * @param {Ext.slider.Thumb} thumb The thumb that the Tip is attached to
130173      */
130174     onSlide : function(slider, e, thumb) {
130175         var me = this;
130176         me.show();
130177         me.update(me.getText(thumb));
130178         me.doComponentLayout();
130179         me.el.alignTo(thumb.el, 'b-t?', me.offsets);
130180     },
130181
130182     /**
130183      * Used to create the text that appears in the Tip's body. By default this just returns the value of the Slider
130184      * Thumb that the Tip is attached to. Override to customize.
130185      * @param {Ext.slider.Thumb} thumb The Thumb that the Tip is attached to
130186      * @return {String} The text to display in the tip
130187      */
130188     getText : function(thumb) {
130189         return String(thumb.value);
130190     }
130191 });
130192 /**
130193  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
130194  * and animation. Can be added as an item to any container.
130195  *
130196  * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
130197  *
130198  *     @example
130199  *     Ext.create('Ext.slider.Multi', {
130200  *         width: 200,
130201  *         values: [25, 50, 75],
130202  *         increment: 5,
130203  *         minValue: 0,
130204  *         maxValue: 100,
130205  *
130206  *         // this defaults to true, setting to false allows the thumbs to pass each other
130207  *         constrainThumbs: false,
130208  *         renderTo: Ext.getBody()
130209  *     });
130210  */
130211 Ext.define('Ext.slider.Multi', {
130212     extend: 'Ext.form.field.Base',
130213     alias: 'widget.multislider',
130214     alternateClassName: 'Ext.slider.MultiSlider',
130215
130216     requires: [
130217         'Ext.slider.Thumb',
130218         'Ext.slider.Tip',
130219         'Ext.Number',
130220         'Ext.util.Format',
130221         'Ext.Template',
130222         'Ext.layout.component.field.Slider'
130223     ],
130224
130225     // note: {id} here is really {inputId}, but {cmpId} is available
130226     fieldSubTpl: [
130227         '<div id="{id}" class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
130228             '<div id="{cmpId}-endEl" class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
130229                 '<div id="{cmpId}-innerEl" class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
130230                     '<a id="{cmpId}-focusEl" class="' + Ext.baseCSSPrefix + 'slider-focus" href="#" tabIndex="-1" hidefocus="on" role="presentation"></a>',
130231                 '</div>',
130232             '</div>',
130233         '</div>',
130234         {
130235             disableFormats: true,
130236             compiled: true
130237         }
130238     ],
130239
130240     /**
130241      * @cfg {Number} value
130242      * A value with which to initialize the slider. Defaults to minValue. Setting this will only result in the creation
130243      * of a single slider thumb; if you want multiple thumbs then use the {@link #values} config instead.
130244      */
130245
130246     /**
130247      * @cfg {Number[]} values
130248      * Array of Number values with which to initalize the slider. A separate slider thumb will be created for each value
130249      * in this array. This will take precedence over the single {@link #value} config.
130250      */
130251
130252     /**
130253      * @cfg {Boolean} vertical
130254      * Orient the Slider vertically rather than horizontally.
130255      */
130256     vertical: false,
130257
130258     /**
130259      * @cfg {Number} minValue
130260      * The minimum value for the Slider.
130261      */
130262     minValue: 0,
130263
130264     /**
130265      * @cfg {Number} maxValue
130266      * The maximum value for the Slider.
130267      */
130268     maxValue: 100,
130269
130270     /**
130271      * @cfg {Number/Boolean} decimalPrecision The number of decimal places to which to round the Slider's value.
130272      *
130273      * To disable rounding, configure as **false**.
130274      */
130275     decimalPrecision: 0,
130276
130277     /**
130278      * @cfg {Number} keyIncrement
130279      * How many units to change the Slider when adjusting with keyboard navigation. If the increment
130280      * config is larger, it will be used instead.
130281      */
130282     keyIncrement: 1,
130283
130284     /**
130285      * @cfg {Number} increment
130286      * How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
130287      */
130288     increment: 0,
130289
130290     /**
130291      * @private
130292      * @property {Number[]} clickRange
130293      * 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],
130294      * 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'
130295      * 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
130296      */
130297     clickRange: [5,15],
130298
130299     /**
130300      * @cfg {Boolean} clickToChange
130301      * Determines whether or not clicking on the Slider axis will change the slider.
130302      */
130303     clickToChange : true,
130304
130305     /**
130306      * @cfg {Boolean} animate
130307      * Turn on or off animation.
130308      */
130309     animate: true,
130310
130311     /**
130312      * @property {Boolean} dragging
130313      * True while the thumb is in a drag operation
130314      */
130315     dragging: false,
130316
130317     /**
130318      * @cfg {Boolean} constrainThumbs
130319      * True to disallow thumbs from overlapping one another.
130320      */
130321     constrainThumbs: true,
130322
130323     componentLayout: 'sliderfield',
130324
130325     /**
130326      * @cfg {Boolean} useTips
130327      * True to use an Ext.slider.Tip to display tips for the value.
130328      */
130329     useTips : true,
130330
130331     /**
130332      * @cfg {Function} tipText
130333      * A function used to display custom text for the slider tip. Defaults to null, which will use the default on the
130334      * plugin.
130335      */
130336     tipText : null,
130337
130338     ariaRole: 'slider',
130339
130340     // private override
130341     initValue: function() {
130342         var me = this,
130343             extValue = Ext.value,
130344             // Fallback for initial values: values config -> value config -> minValue config -> 0
130345             values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
130346             i = 0,
130347             len = values.length;
130348
130349         // Store for use in dirty check
130350         me.originalValue = values;
130351
130352         // Add a thumb for each value
130353         for (; i < len; i++) {
130354             me.addThumb(values[i]);
130355         }
130356     },
130357
130358     // private override
130359     initComponent : function() {
130360         var me = this,
130361             tipPlug,
130362             hasTip;
130363
130364         /**
130365          * @property {Array} thumbs
130366          * Array containing references to each thumb
130367          */
130368         me.thumbs = [];
130369
130370         me.keyIncrement = Math.max(me.increment, me.keyIncrement);
130371
130372         me.addEvents(
130373             /**
130374              * @event beforechange
130375              * Fires before the slider value is changed. By returning false from an event handler, you can cancel the
130376              * event and prevent the slider from changing.
130377              * @param {Ext.slider.Multi} slider The slider
130378              * @param {Number} newValue The new value which the slider is being changed to.
130379              * @param {Number} oldValue The old value which the slider was previously.
130380              */
130381             'beforechange',
130382
130383             /**
130384              * @event change
130385              * Fires when the slider value is changed.
130386              * @param {Ext.slider.Multi} slider The slider
130387              * @param {Number} newValue The new value which the slider has been changed to.
130388              * @param {Ext.slider.Thumb} thumb The thumb that was changed
130389              */
130390             'change',
130391
130392             /**
130393              * @event changecomplete
130394              * Fires when the slider value is changed by the user and any drag operations have completed.
130395              * @param {Ext.slider.Multi} slider The slider
130396              * @param {Number} newValue The new value which the slider has been changed to.
130397              * @param {Ext.slider.Thumb} thumb The thumb that was changed
130398              */
130399             'changecomplete',
130400
130401             /**
130402              * @event dragstart
130403              * Fires after a drag operation has started.
130404              * @param {Ext.slider.Multi} slider The slider
130405              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
130406              */
130407             'dragstart',
130408
130409             /**
130410              * @event drag
130411              * Fires continuously during the drag operation while the mouse is moving.
130412              * @param {Ext.slider.Multi} slider The slider
130413              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
130414              */
130415             'drag',
130416
130417             /**
130418              * @event dragend
130419              * Fires after the drag operation has completed.
130420              * @param {Ext.slider.Multi} slider The slider
130421              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
130422              */
130423             'dragend'
130424         );
130425
130426         if (me.vertical) {
130427             Ext.apply(me, Ext.slider.Multi.Vertical);
130428         }
130429
130430         me.callParent();
130431
130432         // only can use it if it exists.
130433         if (me.useTips) {
130434             tipPlug = me.tipText ? {getText: me.tipText} : {};
130435             me.plugins = me.plugins || [];
130436             Ext.each(me.plugins, function(plug){
130437                 if (plug.isSliderTip) {
130438                     hasTip = true;
130439                     return false;
130440                 }
130441             });
130442             if (!hasTip) {
130443                 me.plugins.push(Ext.create('Ext.slider.Tip', tipPlug));
130444             }
130445         }
130446     },
130447
130448     /**
130449      * Creates a new thumb and adds it to the slider
130450      * @param {Number} value The initial value to set on the thumb. Defaults to 0
130451      * @return {Ext.slider.Thumb} The thumb
130452      */
130453     addThumb: function(value) {
130454         var me = this,
130455             thumb = Ext.create('Ext.slider.Thumb', {
130456             value    : value,
130457             slider   : me,
130458             index    : me.thumbs.length,
130459             constrain: me.constrainThumbs
130460         });
130461         me.thumbs.push(thumb);
130462
130463         //render the thumb now if needed
130464         if (me.rendered) {
130465             thumb.render();
130466         }
130467
130468         return thumb;
130469     },
130470
130471     /**
130472      * @private
130473      * Moves the given thumb above all other by increasing its z-index. This is called when as drag
130474      * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
130475      * required when the thumbs are stacked on top of each other at one of the ends of the slider's
130476      * range, which can result in the user not being able to move any of them.
130477      * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
130478      */
130479     promoteThumb: function(topThumb) {
130480         var thumbs = this.thumbs,
130481             ln = thumbs.length,
130482             zIndex, thumb, i;
130483
130484         for (i = 0; i < ln; i++) {
130485             thumb = thumbs[i];
130486
130487             if (thumb == topThumb) {
130488                 thumb.bringToFront();
130489             } else {
130490                 thumb.sendToBack();
130491             }
130492         }
130493     },
130494
130495     // private override
130496     onRender : function() {
130497         var me = this,
130498             i = 0,
130499             thumbs = me.thumbs,
130500             len = thumbs.length,
130501             thumb;
130502
130503         Ext.applyIf(me.subTplData, {
130504             vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
130505             minValue: me.minValue,
130506             maxValue: me.maxValue,
130507             value: me.value
130508         });
130509
130510         me.addChildEls('endEl', 'innerEl', 'focusEl');
130511
130512         me.callParent(arguments);
130513
130514         //render each thumb
130515         for (; i < len; i++) {
130516             thumbs[i].render();
130517         }
130518
130519         //calculate the size of half a thumb
130520         thumb = me.innerEl.down('.' + Ext.baseCSSPrefix + 'slider-thumb');
130521         me.halfThumb = (me.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
130522
130523     },
130524
130525     /**
130526      * Utility method to set the value of the field when the slider changes.
130527      * @param {Object} slider The slider object.
130528      * @param {Object} v The new value.
130529      * @private
130530      */
130531     onChange : function(slider, v) {
130532         this.setValue(v, undefined, true);
130533     },
130534
130535     /**
130536      * @private
130537      * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
130538      */
130539     initEvents : function() {
130540         var me = this;
130541
130542         me.mon(me.el, {
130543             scope    : me,
130544             mousedown: me.onMouseDown,
130545             keydown  : me.onKeyDown,
130546             change : me.onChange
130547         });
130548
130549         me.focusEl.swallowEvent("click", true);
130550     },
130551
130552     /**
130553      * @private
130554      * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
130555      * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
130556      * @param {Ext.EventObject} e The click event
130557      */
130558     onMouseDown : function(e) {
130559         var me = this,
130560             thumbClicked = false,
130561             i = 0,
130562             thumbs = me.thumbs,
130563             len = thumbs.length,
130564             local;
130565
130566         if (me.disabled) {
130567             return;
130568         }
130569
130570         //see if the click was on any of the thumbs
130571         for (; i < len; i++) {
130572             thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
130573         }
130574
130575         if (me.clickToChange && !thumbClicked) {
130576             local = me.innerEl.translatePoints(e.getXY());
130577             me.onClickChange(local);
130578         }
130579         me.focus();
130580     },
130581
130582     /**
130583      * @private
130584      * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Multi.Vertical.
130585      * Only changes the value if the click was within this.clickRange.
130586      * @param {Object} local Object containing top and left values for the click event.
130587      */
130588     onClickChange : function(local) {
130589         var me = this,
130590             thumb, index;
130591
130592         if (local.top > me.clickRange[0] && local.top < me.clickRange[1]) {
130593             //find the nearest thumb to the click event
130594             thumb = me.getNearest(local, 'left');
130595             if (!thumb.disabled) {
130596                 index = thumb.index;
130597                 me.setValue(index, Ext.util.Format.round(me.reverseValue(local.left), me.decimalPrecision), undefined, true);
130598             }
130599         }
130600     },
130601
130602     /**
130603      * @private
130604      * Returns the nearest thumb to a click event, along with its distance
130605      * @param {Object} local Object containing top and left values from a click event
130606      * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
130607      * @return {Object} The closest thumb object and its distance from the click event
130608      */
130609     getNearest: function(local, prop) {
130610         var me = this,
130611             localValue = prop == 'top' ? me.innerEl.getHeight() - local[prop] : local[prop],
130612             clickValue = me.reverseValue(localValue),
130613             nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
130614             index = 0,
130615             nearest = null,
130616             thumbs = me.thumbs,
130617             i = 0,
130618             len = thumbs.length,
130619             thumb,
130620             value,
130621             dist;
130622
130623         for (; i < len; i++) {
130624             thumb = me.thumbs[i];
130625             value = thumb.value;
130626             dist  = Math.abs(value - clickValue);
130627
130628             if (Math.abs(dist <= nearestDistance)) {
130629                 nearest = thumb;
130630                 index = i;
130631                 nearestDistance = dist;
130632             }
130633         }
130634         return nearest;
130635     },
130636
130637     /**
130638      * @private
130639      * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
130640      * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
130641      * @param {Ext.EventObject} e The Event object
130642      */
130643     onKeyDown : function(e) {
130644         /*
130645          * The behaviour for keyboard handling with multiple thumbs is currently undefined.
130646          * There's no real sane default for it, so leave it like this until we come up
130647          * with a better way of doing it.
130648          */
130649         var me = this,
130650             k,
130651             val;
130652
130653         if(me.disabled || me.thumbs.length !== 1) {
130654             e.preventDefault();
130655             return;
130656         }
130657         k = e.getKey();
130658
130659         switch(k) {
130660             case e.UP:
130661             case e.RIGHT:
130662                 e.stopEvent();
130663                 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
130664                 me.setValue(0, val, undefined, true);
130665             break;
130666             case e.DOWN:
130667             case e.LEFT:
130668                 e.stopEvent();
130669                 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
130670                 me.setValue(0, val, undefined, true);
130671             break;
130672             default:
130673                 e.preventDefault();
130674         }
130675     },
130676
130677     // private
130678     afterRender : function() {
130679         var me = this,
130680             i = 0,
130681             thumbs = me.thumbs,
130682             len = thumbs.length,
130683             thumb,
130684             v;
130685
130686         me.callParent(arguments);
130687
130688         for (; i < len; i++) {
130689             thumb = thumbs[i];
130690
130691             if (thumb.value !== undefined) {
130692                 v = me.normalizeValue(thumb.value);
130693                 if (v !== thumb.value) {
130694                     // delete this.value;
130695                     me.setValue(i, v, false);
130696                 } else {
130697                     thumb.move(me.translateValue(v), false);
130698                 }
130699             }
130700         }
130701     },
130702
130703     /**
130704      * @private
130705      * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
130706      * the ratio is 2
130707      * @return {Number} The ratio of pixels to mapped values
130708      */
130709     getRatio : function() {
130710         var w = this.innerEl.getWidth(),
130711             v = this.maxValue - this.minValue;
130712         return v === 0 ? w : (w/v);
130713     },
130714
130715     /**
130716      * @private
130717      * Returns a snapped, constrained value when given a desired value
130718      * @param {Number} value Raw number value
130719      * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
130720      */
130721     normalizeValue : function(v) {
130722         var me = this;
130723
130724         v = Ext.Number.snap(v, this.increment, this.minValue, this.maxValue);
130725         v = Ext.util.Format.round(v, me.decimalPrecision);
130726         v = Ext.Number.constrain(v, me.minValue, me.maxValue);
130727         return v;
130728     },
130729
130730     /**
130731      * Sets the minimum value for the slider instance. If the current value is less than the minimum value, the current
130732      * value will be changed.
130733      * @param {Number} val The new minimum value
130734      */
130735     setMinValue : function(val) {
130736         var me = this,
130737             i = 0,
130738             thumbs = me.thumbs,
130739             len = thumbs.length,
130740             t;
130741
130742         me.minValue = val;
130743         if (me.rendered) {
130744             me.inputEl.dom.setAttribute('aria-valuemin', val);
130745         }
130746
130747         for (; i < len; ++i) {
130748             t = thumbs[i];
130749             t.value = t.value < val ? val : t.value;
130750         }
130751         me.syncThumbs();
130752     },
130753
130754     /**
130755      * Sets the maximum value for the slider instance. If the current value is more than the maximum value, the current
130756      * value will be changed.
130757      * @param {Number} val The new maximum value
130758      */
130759     setMaxValue : function(val) {
130760         var me = this,
130761             i = 0,
130762             thumbs = me.thumbs,
130763             len = thumbs.length,
130764             t;
130765
130766         me.maxValue = val;
130767         if (me.rendered) {
130768             me.inputEl.dom.setAttribute('aria-valuemax', val);
130769         }
130770
130771         for (; i < len; ++i) {
130772             t = thumbs[i];
130773             t.value = t.value > val ? val : t.value;
130774         }
130775         me.syncThumbs();
130776     },
130777
130778     /**
130779      * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
130780      * maxValue.
130781      * @param {Number} index Index of the thumb to move
130782      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
130783      * @param {Boolean} [animate=true] Turn on or off animation
130784      */
130785     setValue : function(index, value, animate, changeComplete) {
130786         var me = this,
130787             thumb = me.thumbs[index];
130788
130789         // ensures value is contstrained and snapped
130790         value = me.normalizeValue(value);
130791
130792         if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
130793             thumb.value = value;
130794             if (me.rendered) {
130795                 // TODO this only handles a single value; need a solution for exposing multiple values to aria.
130796                 // Perhaps this should go on each thumb element rather than the outer element.
130797                 me.inputEl.set({
130798                     'aria-valuenow': value,
130799                     'aria-valuetext': value
130800                 });
130801
130802                 thumb.move(me.translateValue(value), Ext.isDefined(animate) ? animate !== false : me.animate);
130803
130804                 me.fireEvent('change', me, value, thumb);
130805                 if (changeComplete) {
130806                     me.fireEvent('changecomplete', me, value, thumb);
130807                 }
130808             }
130809         }
130810     },
130811
130812     /**
130813      * @private
130814      */
130815     translateValue : function(v) {
130816         var ratio = this.getRatio();
130817         return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
130818     },
130819
130820     /**
130821      * @private
130822      * Given a pixel location along the slider, returns the mapped slider value for that pixel.
130823      * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
130824      * returns 200
130825      * @param {Number} pos The position along the slider to return a mapped value for
130826      * @return {Number} The mapped value for the given position
130827      */
130828     reverseValue : function(pos) {
130829         var ratio = this.getRatio();
130830         return (pos + (this.minValue * ratio)) / ratio;
130831     },
130832
130833     // private
130834     focus : function() {
130835         this.focusEl.focus(10);
130836     },
130837
130838     //private
130839     onDisable: function() {
130840         var me = this,
130841             i = 0,
130842             thumbs = me.thumbs,
130843             len = thumbs.length,
130844             thumb,
130845             el,
130846             xy;
130847
130848         me.callParent();
130849
130850         for (; i < len; i++) {
130851             thumb = thumbs[i];
130852             el = thumb.el;
130853
130854             thumb.disable();
130855
130856             if(Ext.isIE) {
130857                 //IE breaks when using overflow visible and opacity other than 1.
130858                 //Create a place holder for the thumb and display it.
130859                 xy = el.getXY();
130860                 el.hide();
130861
130862                 me.innerEl.addCls(me.disabledCls).dom.disabled = true;
130863
130864                 if (!me.thumbHolder) {
130865                     me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
130866                 }
130867
130868                 me.thumbHolder.show().setXY(xy);
130869             }
130870         }
130871     },
130872
130873     //private
130874     onEnable: function() {
130875         var me = this,
130876             i = 0,
130877             thumbs = me.thumbs,
130878             len = thumbs.length,
130879             thumb,
130880             el;
130881
130882         this.callParent();
130883
130884         for (; i < len; i++) {
130885             thumb = thumbs[i];
130886             el = thumb.el;
130887
130888             thumb.enable();
130889
130890             if (Ext.isIE) {
130891                 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
130892
130893                 if (me.thumbHolder) {
130894                     me.thumbHolder.hide();
130895                 }
130896
130897                 el.show();
130898                 me.syncThumbs();
130899             }
130900         }
130901     },
130902
130903     /**
130904      * Synchronizes thumbs position to the proper proportion of the total component width based on the current slider
130905      * {@link #value}. This will be called automatically when the Slider is resized by a layout, but if it is rendered
130906      * auto width, this method can be called from another resize handler to sync the Slider if necessary.
130907      */
130908     syncThumbs : function() {
130909         if (this.rendered) {
130910             var thumbs = this.thumbs,
130911                 length = thumbs.length,
130912                 i = 0;
130913
130914             for (; i < length; i++) {
130915                 thumbs[i].move(this.translateValue(thumbs[i].value));
130916             }
130917         }
130918     },
130919
130920     /**
130921      * Returns the current value of the slider
130922      * @param {Number} index The index of the thumb to return a value for
130923      * @return {Number/Number[]} The current value of the slider at the given index, or an array of all thumb values if
130924      * no index is given.
130925      */
130926     getValue : function(index) {
130927         return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
130928     },
130929
130930     /**
130931      * Returns an array of values - one for the location of each thumb
130932      * @return {Number[]} The set of thumb values
130933      */
130934     getValues: function() {
130935         var values = [],
130936             i = 0,
130937             thumbs = this.thumbs,
130938             len = thumbs.length;
130939
130940         for (; i < len; i++) {
130941             values.push(thumbs[i].value);
130942         }
130943
130944         return values;
130945     },
130946
130947     getSubmitValue: function() {
130948         var me = this;
130949         return (me.disabled || !me.submitValue) ? null : me.getValue();
130950     },
130951
130952     reset: function() {
130953         var me = this,
130954             Array = Ext.Array;
130955         Array.forEach(Array.from(me.originalValue), function(val, i) {
130956             me.setValue(i, val);
130957         });
130958         me.clearInvalid();
130959         // delete here so we reset back to the original state
130960         delete me.wasValid;
130961     },
130962
130963     // private
130964     beforeDestroy : function() {
130965         var me = this;
130966
130967         Ext.destroy(me.innerEl, me.endEl, me.focusEl);
130968         Ext.each(me.thumbs, function(thumb) {
130969             Ext.destroy(thumb);
130970         }, me);
130971
130972         me.callParent();
130973     },
130974
130975     statics: {
130976         // Method overrides to support slider with vertical orientation
130977         Vertical: {
130978             getRatio : function() {
130979                 var h = this.innerEl.getHeight(),
130980                     v = this.maxValue - this.minValue;
130981                 return h/v;
130982             },
130983
130984             onClickChange : function(local) {
130985                 var me = this,
130986                     thumb, index, bottom;
130987
130988                 if (local.left > me.clickRange[0] && local.left < me.clickRange[1]) {
130989                     thumb = me.getNearest(local, 'top');
130990                     if (!thumb.disabled) {
130991                         index = thumb.index;
130992                         bottom =  me.reverseValue(me.innerEl.getHeight() - local.top);
130993
130994                         me.setValue(index, Ext.util.Format.round(me.minValue + bottom, me.decimalPrecision), undefined, true);
130995                     }
130996                 }
130997             }
130998         }
130999     }
131000 });
131001
131002 /**
131003  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
131004  * and animation. Can be added as an item to any container.
131005  *
131006  *     @example
131007  *     Ext.create('Ext.slider.Single', {
131008  *         width: 200,
131009  *         value: 50,
131010  *         increment: 10,
131011  *         minValue: 0,
131012  *         maxValue: 100,
131013  *         renderTo: Ext.getBody()
131014  *     });
131015  *
131016  * The class Ext.slider.Single is aliased to Ext.Slider for backwards compatibility.
131017  */
131018 Ext.define('Ext.slider.Single', {
131019     extend: 'Ext.slider.Multi',
131020     alias: ['widget.slider', 'widget.sliderfield'],
131021     alternateClassName: ['Ext.Slider', 'Ext.form.SliderField', 'Ext.slider.SingleSlider', 'Ext.slider.Slider'],
131022
131023     /**
131024      * Returns the current value of the slider
131025      * @return {Number} The current value of the slider
131026      */
131027     getValue: function() {
131028         // just returns the value of the first thumb, which should be the only one in a single slider
131029         return this.callParent([0]);
131030     },
131031
131032     /**
131033      * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
131034      * maxValue.
131035      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
131036      * @param {Boolean} [animate] Turn on or off animation
131037      */
131038     setValue: function(value, animate) {
131039         var args = Ext.toArray(arguments),
131040             len  = args.length;
131041
131042         // this is to maintain backwards compatiblity for sliders with only one thunb. Usually you must pass the thumb
131043         // index to setValue, but if we only have one thumb we inject the index here first if given the multi-slider
131044         // signature without the required index. The index will always be 0 for a single slider
131045         if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) {
131046             args.unshift(0);
131047         }
131048
131049         return this.callParent(args);
131050     },
131051
131052     // private
131053     getNearest : function(){
131054         // Since there's only 1 thumb, it's always the nearest
131055         return this.thumbs[0];
131056     }
131057 });
131058
131059 /**
131060  * @author Ed Spencer
131061  * @class Ext.tab.Tab
131062  * @extends Ext.button.Button
131063  *
131064  * <p>Represents a single Tab in a {@link Ext.tab.Panel TabPanel}. A Tab is simply a slightly customized {@link Ext.button.Button Button},
131065  * styled to look like a tab. Tabs are optionally closable, and can also be disabled. Typically you will not
131066  * need to create Tabs manually as the framework does so automatically when you use a {@link Ext.tab.Panel TabPanel}</p>
131067  */
131068 Ext.define('Ext.tab.Tab', {
131069     extend: 'Ext.button.Button',
131070     alias: 'widget.tab',
131071
131072     requires: [
131073         'Ext.layout.component.Tab',
131074         'Ext.util.KeyNav'
131075     ],
131076
131077     componentLayout: 'tab',
131078
131079     isTab: true,
131080
131081     baseCls: Ext.baseCSSPrefix + 'tab',
131082
131083     /**
131084      * @cfg {String} activeCls
131085      * The CSS class to be applied to a Tab when it is active.
131086      * Providing your own CSS for this class enables you to customize the active state.
131087      */
131088     activeCls: 'active',
131089
131090     /**
131091      * @cfg {String} disabledCls
131092      * The CSS class to be applied to a Tab when it is disabled.
131093      */
131094
131095     /**
131096      * @cfg {String} closableCls
131097      * The CSS class which is added to the tab when it is closable
131098      */
131099     closableCls: 'closable',
131100
131101     /**
131102      * @cfg {Boolean} closable True to make the Tab start closable (the close icon will be visible).
131103      */
131104     closable: true,
131105
131106     /**
131107      * @cfg {String} closeText
131108      * The accessible text label for the close button link; only used when {@link #closable} = true.
131109      */
131110     closeText: 'Close Tab',
131111
131112     /**
131113      * @property {Boolean} active
131114      * Read-only property indicating that this tab is currently active. This is NOT a public configuration.
131115      */
131116     active: false,
131117
131118     /**
131119      * @property closable
131120      * @type Boolean
131121      * True if the tab is currently closable
131122      */
131123
131124     scale: false,
131125
131126     position: 'top',
131127
131128     initComponent: function() {
131129         var me = this;
131130
131131         me.addEvents(
131132             /**
131133              * @event activate
131134              * Fired when the tab is activated.
131135              * @param {Ext.tab.Tab} this
131136              */
131137             'activate',
131138
131139             /**
131140              * @event deactivate
131141              * Fired when the tab is deactivated.
131142              * @param {Ext.tab.Tab} this
131143              */
131144             'deactivate',
131145
131146             /**
131147              * @event beforeclose
131148              * Fires if the user clicks on the Tab's close button, but before the {@link #close} event is fired. Return
131149              * false from any listener to stop the close event being fired
131150              * @param {Ext.tab.Tab} tab The Tab object
131151              */
131152             'beforeclose',
131153
131154             /**
131155              * @event close
131156              * Fires to indicate that the tab is to be closed, usually because the user has clicked the close button.
131157              * @param {Ext.tab.Tab} tab The Tab object
131158              */
131159             'close'
131160         );
131161
131162         me.callParent(arguments);
131163
131164         if (me.card) {
131165             me.setCard(me.card);
131166         }
131167     },
131168
131169     /**
131170      * @ignore
131171      */
131172     onRender: function() {
131173         var me = this,
131174             tabBar = me.up('tabbar'),
131175             tabPanel = me.up('tabpanel');
131176
131177         me.addClsWithUI(me.position);
131178
131179         // Set all the state classNames, as they need to include the UI
131180         // me.disabledCls = me.getClsWithUIs('disabled');
131181
131182         me.syncClosableUI();
131183
131184         // Propagate minTabWidth and maxTabWidth settings from the owning TabBar then TabPanel
131185         if (!me.minWidth) {
131186             me.minWidth = (tabBar) ? tabBar.minTabWidth : me.minWidth;
131187             if (!me.minWidth && tabPanel) {
131188                 me.minWidth = tabPanel.minTabWidth;
131189             }
131190             if (me.minWidth && me.iconCls) {
131191                 me.minWidth += 25;
131192             }
131193         }
131194         if (!me.maxWidth) {
131195             me.maxWidth = (tabBar) ? tabBar.maxTabWidth : me.maxWidth;
131196             if (!me.maxWidth && tabPanel) {
131197                 me.maxWidth = tabPanel.maxTabWidth;
131198             }
131199         }
131200
131201         me.callParent(arguments);
131202
131203         if (me.active) {
131204             me.activate(true);
131205         }
131206
131207         me.syncClosableElements();
131208
131209         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
131210             enter: me.onEnterKey,
131211             del: me.onDeleteKey,
131212             scope: me
131213         });
131214     },
131215
131216     // inherit docs
131217     enable : function(silent) {
131218         var me = this;
131219
131220         me.callParent(arguments);
131221
131222         me.removeClsWithUI(me.position + '-disabled');
131223
131224         return me;
131225     },
131226
131227     // inherit docs
131228     disable : function(silent) {
131229         var me = this;
131230
131231         me.callParent(arguments);
131232
131233         me.addClsWithUI(me.position + '-disabled');
131234
131235         return me;
131236     },
131237
131238     /**
131239      * @ignore
131240      */
131241     onDestroy: function() {
131242         var me = this;
131243
131244         if (me.closeEl) {
131245             me.closeEl.un('click', Ext.EventManager.preventDefault);
131246             me.closeEl = null;
131247         }
131248
131249         Ext.destroy(me.keyNav);
131250         delete me.keyNav;
131251
131252         me.callParent(arguments);
131253     },
131254
131255     /**
131256      * Sets the tab as either closable or not
131257      * @param {Boolean} closable Pass false to make the tab not closable. Otherwise the tab will be made closable (eg a
131258      * close button will appear on the tab)
131259      */
131260     setClosable: function(closable) {
131261         var me = this;
131262
131263         // Closable must be true if no args
131264         closable = (!arguments.length || !!closable);
131265
131266         if (me.closable != closable) {
131267             me.closable = closable;
131268
131269             // set property on the user-facing item ('card'):
131270             if (me.card) {
131271                 me.card.closable = closable;
131272             }
131273
131274             me.syncClosableUI();
131275
131276             if (me.rendered) {
131277                 me.syncClosableElements();
131278
131279                 // Tab will change width to accommodate close icon
131280                 me.doComponentLayout();
131281                 if (me.ownerCt) {
131282                     me.ownerCt.doLayout();
131283                 }
131284             }
131285         }
131286     },
131287
131288     /**
131289      * This method ensures that the closeBtn element exists or not based on 'closable'.
131290      * @private
131291      */
131292     syncClosableElements: function () {
131293         var me = this;
131294
131295         if (me.closable) {
131296             if (!me.closeEl) {
131297                 me.closeEl = me.el.createChild({
131298                     tag: 'a',
131299                     cls: me.baseCls + '-close-btn',
131300                     href: '#',
131301                     // html: me.closeText, // removed for EXTJSIV-1719, by rob@sencha.com
131302                     title: me.closeText
131303                 }).on('click', Ext.EventManager.preventDefault);  // mon ???
131304             }
131305         } else {
131306             var closeEl = me.closeEl;
131307             if (closeEl) {
131308                 closeEl.un('click', Ext.EventManager.preventDefault);
131309                 closeEl.remove();
131310                 me.closeEl = null;
131311             }
131312         }
131313     },
131314
131315     /**
131316      * This method ensures that the UI classes are added or removed based on 'closable'.
131317      * @private
131318      */
131319     syncClosableUI: function () {
131320         var me = this, classes = [me.closableCls, me.closableCls + '-' + me.position];
131321
131322         if (me.closable) {
131323             me.addClsWithUI(classes);
131324         } else {
131325             me.removeClsWithUI(classes);
131326         }
131327     },
131328
131329     /**
131330      * Sets this tab's attached card. Usually this is handled automatically by the {@link Ext.tab.Panel} that this Tab
131331      * belongs to and would not need to be done by the developer
131332      * @param {Ext.Component} card The card to set
131333      */
131334     setCard: function(card) {
131335         var me = this;
131336
131337         me.card = card;
131338         me.setText(me.title || card.title);
131339         me.setIconCls(me.iconCls || card.iconCls);
131340     },
131341
131342     /**
131343      * @private
131344      * Listener attached to click events on the Tab's close button
131345      */
131346     onCloseClick: function() {
131347         var me = this;
131348
131349         if (me.fireEvent('beforeclose', me) !== false) {
131350             if (me.tabBar) {
131351                 if (me.tabBar.closeTab(me) === false) {
131352                     // beforeclose on the panel vetoed the event, stop here
131353                     return;
131354                 }
131355             } else {
131356                 // if there's no tabbar, fire the close event
131357                 me.fireEvent('close', me);
131358             }
131359         }
131360     },
131361
131362     /**
131363      * Fires the close event on the tab.
131364      * @private
131365      */
131366     fireClose: function(){
131367         this.fireEvent('close', this);
131368     },
131369
131370     /**
131371      * @private
131372      */
131373     onEnterKey: function(e) {
131374         var me = this;
131375
131376         if (me.tabBar) {
131377             me.tabBar.onClick(e, me.el);
131378         }
131379     },
131380
131381    /**
131382      * @private
131383      */
131384     onDeleteKey: function(e) {
131385         var me = this;
131386
131387         if (me.closable) {
131388             me.onCloseClick();
131389         }
131390     },
131391
131392     // @private
131393     activate : function(supressEvent) {
131394         var me = this;
131395
131396         me.active = true;
131397         me.addClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
131398
131399         if (supressEvent !== true) {
131400             me.fireEvent('activate', me);
131401         }
131402     },
131403
131404     // @private
131405     deactivate : function(supressEvent) {
131406         var me = this;
131407
131408         me.active = false;
131409         me.removeClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
131410
131411         if (supressEvent !== true) {
131412             me.fireEvent('deactivate', me);
131413         }
131414     }
131415 });
131416
131417 /**
131418  * @author Ed Spencer
131419  * TabBar is used internally by a {@link Ext.tab.Panel TabPanel} and typically should not need to be created manually.
131420  * The tab bar automatically removes the default title provided by {@link Ext.panel.Header}
131421  */
131422 Ext.define('Ext.tab.Bar', {
131423     extend: 'Ext.panel.Header',
131424     alias: 'widget.tabbar',
131425     baseCls: Ext.baseCSSPrefix + 'tab-bar',
131426
131427     requires: [
131428         'Ext.tab.Tab',
131429         'Ext.FocusManager'
131430     ],
131431
131432     isTabBar: true,
131433     
131434     /**
131435      * @cfg {String} title @hide
131436      */
131437     
131438     /**
131439      * @cfg {String} iconCls @hide
131440      */
131441
131442     // @private
131443     defaultType: 'tab',
131444
131445     /**
131446      * @cfg {Boolean} plain
131447      * True to not show the full background on the tabbar
131448      */
131449     plain: false,
131450
131451     // @private
131452     renderTpl: [
131453         '<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>',
131454         '<div id="{id}-strip" class="{baseCls}-strip<tpl if="ui"> {baseCls}-strip-{ui}<tpl for="uiCls"> {parent.baseCls}-strip-{parent.ui}-{.}</tpl></tpl>"></div>'
131455     ],
131456
131457     /**
131458      * @cfg {Number} minTabWidth
131459      * The minimum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#minTabWidth minTabWidth} value.
131460      * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#minTabWidth minTabWidth} config on the TabPanel.
131461      */
131462
131463     /**
131464      * @cfg {Number} maxTabWidth
131465      * The maximum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#maxTabWidth maxTabWidth} value.
131466      * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#maxTabWidth maxTabWidth} config on the TabPanel.
131467      */
131468
131469     // @private
131470     initComponent: function() {
131471         var me = this,
131472             keys;
131473
131474         if (me.plain) {
131475             me.setUI(me.ui + '-plain');
131476         }
131477
131478         me.addClsWithUI(me.dock);
131479
131480         me.addEvents(
131481             /**
131482              * @event change
131483              * Fired when the currently-active tab has changed
131484              * @param {Ext.tab.Bar} tabBar The TabBar
131485              * @param {Ext.tab.Tab} tab The new Tab
131486              * @param {Ext.Component} card The card that was just shown in the TabPanel
131487              */
131488             'change'
131489         );
131490
131491         me.addChildEls('body', 'strip');
131492         me.callParent(arguments);
131493
131494         // TabBar must override the Header's align setting.
131495         me.layout.align = (me.orientation == 'vertical') ? 'left' : 'top';
131496         me.layout.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.Scroller', me.layout);
131497
131498         me.remove(me.titleCmp);
131499         delete me.titleCmp;
131500
131501         // Subscribe to Ext.FocusManager for key navigation
131502         keys = me.orientation == 'vertical' ? ['up', 'down'] : ['left', 'right'];
131503         Ext.FocusManager.subscribe(me, {
131504             keys: keys
131505         });
131506
131507         Ext.apply(me.renderData, {
131508             bodyCls: me.bodyCls
131509         });
131510     },
131511
131512     // @private
131513     onAdd: function(tab) {
131514         tab.position = this.dock;
131515         this.callParent(arguments);
131516     },
131517     
131518     onRemove: function(tab) {
131519         var me = this;
131520         
131521         if (tab === me.previousTab) {
131522             me.previousTab = null;
131523         }
131524         if (me.items.getCount() === 0) {
131525             me.activeTab = null;
131526         }
131527         me.callParent(arguments);    
131528     },
131529
131530     // @private
131531     afterRender: function() {
131532         var me = this;
131533
131534         me.mon(me.el, {
131535             scope: me,
131536             click: me.onClick,
131537             delegate: '.' + Ext.baseCSSPrefix + 'tab'
131538         });
131539         me.callParent(arguments);
131540
131541     },
131542
131543     afterComponentLayout : function() {
131544         var me = this;
131545
131546         me.callParent(arguments);
131547         me.strip.setWidth(me.el.getWidth());
131548     },
131549
131550     // @private
131551     onClick: function(e, target) {
131552         // The target might not be a valid tab el.
131553         var tab = Ext.getCmp(target.id),
131554             tabPanel = this.tabPanel;
131555
131556         target = e.getTarget();
131557
131558         if (tab && tab.isDisabled && !tab.isDisabled()) {
131559             if (tab.closable && target === tab.closeEl.dom) {
131560                 tab.onCloseClick();
131561             } else {
131562                 if (tabPanel) {
131563                     // TabPanel will card setActiveTab of the TabBar
131564                     tabPanel.setActiveTab(tab.card);
131565                 } else {
131566                     this.setActiveTab(tab);
131567                 }
131568                 tab.focus();
131569             }
131570         }
131571     },
131572
131573     /**
131574      * @private
131575      * Closes the given tab by removing it from the TabBar and removing the corresponding card from the TabPanel
131576      * @param {Ext.tab.Tab} tab The tab to close
131577      */
131578     closeTab: function(tab) {
131579         var me = this,
131580             card = tab.card,
131581             tabPanel = me.tabPanel,
131582             nextTab;
131583
131584         if (card && card.fireEvent('beforeclose', card) === false) {
131585             return false;
131586         }
131587
131588         if (tab.active && me.items.getCount() > 1) {
131589             nextTab = me.previousTab || tab.next('tab') || me.items.first();
131590             me.setActiveTab(nextTab);
131591             if (tabPanel) {
131592                 tabPanel.setActiveTab(nextTab.card);
131593             }
131594         }
131595         /*
131596          * force the close event to fire. By the time this function returns,
131597          * the tab is already destroyed and all listeners have been purged
131598          * so the tab can't fire itself.
131599          */
131600         tab.fireClose();
131601         me.remove(tab);
131602
131603         if (tabPanel && card) {
131604             card.fireEvent('close', card);
131605             tabPanel.remove(card);
131606         }
131607
131608         if (nextTab) {
131609             nextTab.focus();
131610         }
131611     },
131612
131613     /**
131614      * @private
131615      * Marks the given tab as active
131616      * @param {Ext.tab.Tab} tab The tab to mark active
131617      */
131618     setActiveTab: function(tab) {
131619         if (tab.disabled) {
131620             return;
131621         }
131622         var me = this;
131623         if (me.activeTab) {
131624             me.previousTab = me.activeTab;
131625             me.activeTab.deactivate();
131626         }
131627         tab.activate();
131628
131629         if (me.rendered) {
131630             me.layout.layout();
131631             tab.el && tab.el.scrollIntoView(me.layout.getRenderTarget());
131632         }
131633         me.activeTab = tab;
131634         me.fireEvent('change', me, tab, tab.card);
131635     }
131636 });
131637
131638 /**
131639  * @author Ed Spencer, Tommy Maintz, Brian Moeskau
131640  *
131641  * A basic tab container. TabPanels can be used exactly like a standard {@link Ext.panel.Panel} for
131642  * layout purposes, but also have special support for containing child Components
131643  * (`{@link Ext.container.Container#items items}`) that are managed using a
131644  * {@link Ext.layout.container.Card CardLayout layout manager}, and displayed as separate tabs.
131645  *
131646  * **Note:** By default, a tab's close tool _destroys_ the child tab Component and all its descendants.
131647  * This makes the child tab Component, and all its descendants **unusable**.  To enable re-use of a tab,
131648  * configure the TabPanel with `{@link #autoDestroy autoDestroy: false}`.
131649  *
131650  * ## TabPanel's layout
131651  *
131652  * TabPanels use a Dock layout to position the {@link Ext.tab.Bar TabBar} at the top of the widget.
131653  * Panels added to the TabPanel will have their header hidden by default because the Tab will
131654  * automatically take the Panel's configured title and icon.
131655  *
131656  * TabPanels use their {@link Ext.panel.Header header} or {@link Ext.panel.Panel#fbar footer}
131657  * element (depending on the {@link #tabPosition} configuration) to accommodate the tab selector buttons.
131658  * This means that a TabPanel will not display any configured title, and will not display any configured
131659  * header {@link Ext.panel.Panel#tools tools}.
131660  *
131661  * To display a header, embed the TabPanel in a {@link Ext.panel.Panel Panel} which uses
131662  * `{@link Ext.container.Container#layout layout: 'fit'}`.
131663  *
131664  * ## Controlling tabs
131665  *
131666  * Configuration options for the {@link Ext.tab.Tab} that represents the component can be passed in
131667  * by specifying the tabConfig option:
131668  *
131669  *     @example
131670  *     Ext.create('Ext.tab.Panel', {
131671  *         width: 400,
131672  *         height: 400,
131673  *         renderTo: document.body,
131674  *         items: [{
131675  *             title: 'Foo'
131676  *         }, {
131677  *             title: 'Bar',
131678  *             tabConfig: {
131679  *                 title: 'Custom Title',
131680  *                 tooltip: 'A button tooltip'
131681  *             }
131682  *         }]
131683  *     });
131684  *
131685  * # Examples
131686  *
131687  * Here is a basic TabPanel rendered to the body. This also shows the useful configuration {@link #activeTab},
131688  * which allows you to set the active tab on render. If you do not set an {@link #activeTab}, no tabs will be
131689  * active by default.
131690  *
131691  *     @example
131692  *     Ext.create('Ext.tab.Panel', {
131693  *         width: 300,
131694  *         height: 200,
131695  *         activeTab: 0,
131696  *         items: [
131697  *             {
131698  *                 title: 'Tab 1',
131699  *                 bodyPadding: 10,
131700  *                 html : 'A simple tab'
131701  *             },
131702  *             {
131703  *                 title: 'Tab 2',
131704  *                 html : 'Another one'
131705  *             }
131706  *         ],
131707  *         renderTo : Ext.getBody()
131708  *     });
131709  *
131710  * It is easy to control the visibility of items in the tab bar. Specify hidden: true to have the
131711  * tab button hidden initially. Items can be subsequently hidden and show by accessing the
131712  * tab property on the child item.
131713  *
131714  *     @example
131715  *     var tabs = Ext.create('Ext.tab.Panel', {
131716  *         width: 400,
131717  *         height: 400,
131718  *         renderTo: document.body,
131719  *         items: [{
131720  *             title: 'Home',
131721  *             html: 'Home',
131722  *             itemId: 'home'
131723  *         }, {
131724  *             title: 'Users',
131725  *             html: 'Users',
131726  *             itemId: 'users',
131727  *             hidden: true
131728  *         }, {
131729  *             title: 'Tickets',
131730  *             html: 'Tickets',
131731  *             itemId: 'tickets'
131732  *         }]
131733  *     });
131734  *
131735  *     setTimeout(function(){
131736  *         tabs.child('#home').tab.hide();
131737  *         var users = tabs.child('#users');
131738  *         users.tab.show();
131739  *         tabs.setActiveTab(users);
131740  *     }, 1000);
131741  *
131742  * You can remove the background of the TabBar by setting the {@link #plain} property to `true`.
131743  *
131744  *     @example
131745  *     Ext.create('Ext.tab.Panel', {
131746  *         width: 300,
131747  *         height: 200,
131748  *         activeTab: 0,
131749  *         plain: true,
131750  *         items: [
131751  *             {
131752  *                 title: 'Tab 1',
131753  *                 bodyPadding: 10,
131754  *                 html : 'A simple tab'
131755  *             },
131756  *             {
131757  *                 title: 'Tab 2',
131758  *                 html : 'Another one'
131759  *             }
131760  *         ],
131761  *         renderTo : Ext.getBody()
131762  *     });
131763  *
131764  * Another useful configuration of TabPanel is {@link #tabPosition}. This allows you to change the
131765  * position where the tabs are displayed. The available options for this are `'top'` (default) and
131766  * `'bottom'`.
131767  *
131768  *     @example
131769  *     Ext.create('Ext.tab.Panel', {
131770  *         width: 300,
131771  *         height: 200,
131772  *         activeTab: 0,
131773  *         bodyPadding: 10,
131774  *         tabPosition: 'bottom',
131775  *         items: [
131776  *             {
131777  *                 title: 'Tab 1',
131778  *                 html : 'A simple tab'
131779  *             },
131780  *             {
131781  *                 title: 'Tab 2',
131782  *                 html : 'Another one'
131783  *             }
131784  *         ],
131785  *         renderTo : Ext.getBody()
131786  *     });
131787  *
131788  * The {@link #setActiveTab} is a very useful method in TabPanel which will allow you to change the
131789  * current active tab. You can either give it an index or an instance of a tab. For example:
131790  *
131791  *     @example
131792  *     var tabs = Ext.create('Ext.tab.Panel', {
131793  *         items: [
131794  *             {
131795  *                 id   : 'my-tab',
131796  *                 title: 'Tab 1',
131797  *                 html : 'A simple tab'
131798  *             },
131799  *             {
131800  *                 title: 'Tab 2',
131801  *                 html : 'Another one'
131802  *             }
131803  *         ],
131804  *         renderTo : Ext.getBody()
131805  *     });
131806  *
131807  *     var tab = Ext.getCmp('my-tab');
131808  *
131809  *     Ext.create('Ext.button.Button', {
131810  *         renderTo: Ext.getBody(),
131811  *         text    : 'Select the first tab',
131812  *         scope   : this,
131813  *         handler : function() {
131814  *             tabs.setActiveTab(tab);
131815  *         }
131816  *     });
131817  *
131818  *     Ext.create('Ext.button.Button', {
131819  *         text    : 'Select the second tab',
131820  *         scope   : this,
131821  *         handler : function() {
131822  *             tabs.setActiveTab(1);
131823  *         },
131824  *         renderTo : Ext.getBody()
131825  *     });
131826  *
131827  * The {@link #getActiveTab} is a another useful method in TabPanel which will return the current active tab.
131828  *
131829  *     @example
131830  *     var tabs = Ext.create('Ext.tab.Panel', {
131831  *         items: [
131832  *             {
131833  *                 title: 'Tab 1',
131834  *                 html : 'A simple tab'
131835  *             },
131836  *             {
131837  *                 title: 'Tab 2',
131838  *                 html : 'Another one'
131839  *             }
131840  *         ],
131841  *         renderTo : Ext.getBody()
131842  *     });
131843  *
131844  *     Ext.create('Ext.button.Button', {
131845  *         text    : 'Get active tab',
131846  *         scope   : this,
131847  *         handler : function() {
131848  *             var tab = tabs.getActiveTab();
131849  *             alert('Current tab: ' + tab.title);
131850  *         },
131851  *         renderTo : Ext.getBody()
131852  *     });
131853  *
131854  * Adding a new tab is very simple with a TabPanel. You simple call the {@link #add} method with an config
131855  * object for a panel.
131856  *
131857  *     @example
131858  *     var tabs = Ext.create('Ext.tab.Panel', {
131859  *         items: [
131860  *             {
131861  *                 title: 'Tab 1',
131862  *                 html : 'A simple tab'
131863  *             },
131864  *             {
131865  *                 title: 'Tab 2',
131866  *                 html : 'Another one'
131867  *             }
131868  *         ],
131869  *         renderTo : Ext.getBody()
131870  *     });
131871  *
131872  *     Ext.create('Ext.button.Button', {
131873  *         text    : 'New tab',
131874  *         scope   : this,
131875  *         handler : function() {
131876  *             var tab = tabs.add({
131877  *                 // we use the tabs.items property to get the length of current items/tabs
131878  *                 title: 'Tab ' + (tabs.items.length + 1),
131879  *                 html : 'Another one'
131880  *             });
131881  *
131882  *             tabs.setActiveTab(tab);
131883  *         },
131884  *         renderTo : Ext.getBody()
131885  *     });
131886  *
131887  * Additionally, removing a tab is very also simple with a TabPanel. You simple call the {@link #remove} method
131888  * with an config object for a panel.
131889  *
131890  *     @example
131891  *     var tabs = Ext.create('Ext.tab.Panel', {
131892  *         items: [
131893  *             {
131894  *                 title: 'Tab 1',
131895  *                 html : 'A simple tab'
131896  *             },
131897  *             {
131898  *                 id   : 'remove-this-tab',
131899  *                 title: 'Tab 2',
131900  *                 html : 'Another one'
131901  *             }
131902  *         ],
131903  *         renderTo : Ext.getBody()
131904  *     });
131905  *
131906  *     Ext.create('Ext.button.Button', {
131907  *         text    : 'Remove tab',
131908  *         scope   : this,
131909  *         handler : function() {
131910  *             var tab = Ext.getCmp('remove-this-tab');
131911  *             tabs.remove(tab);
131912  *         },
131913  *         renderTo : Ext.getBody()
131914  *     });
131915  */
131916 Ext.define('Ext.tab.Panel', {
131917     extend: 'Ext.panel.Panel',
131918     alias: 'widget.tabpanel',
131919     alternateClassName: ['Ext.TabPanel'],
131920
131921     requires: ['Ext.layout.container.Card', 'Ext.tab.Bar'],
131922
131923     /**
131924      * @cfg {String} tabPosition
131925      * The position where the tab strip should be rendered. Can be `top` or `bottom`.
131926      */
131927     tabPosition : 'top',
131928
131929     /**
131930      * @cfg {String/Number} activeItem
131931      * Doesn't apply for {@link Ext.tab.Panel TabPanel}, use {@link #activeTab} instead.
131932      */
131933
131934     /**
131935      * @cfg {String/Number/Ext.Component} activeTab
131936      * The tab to activate initially. Either an ID, index or the tab component itself.
131937      */
131938
131939     /**
131940      * @cfg {Object} tabBar
131941      * Optional configuration object for the internal {@link Ext.tab.Bar}.
131942      * If present, this is passed straight through to the TabBar's constructor
131943      */
131944
131945     /**
131946      * @cfg {Object} layout
131947      * Optional configuration object for the internal {@link Ext.layout.container.Card card layout}.
131948      * If present, this is passed straight through to the layout's constructor
131949      */
131950
131951     /**
131952      * @cfg {Boolean} removePanelHeader
131953      * True to instruct each Panel added to the TabContainer to not render its header element.
131954      * This is to ensure that the title of the panel does not appear twice.
131955      */
131956     removePanelHeader: true,
131957
131958     /**
131959      * @cfg {Boolean} plain
131960      * True to not show the full background on the TabBar.
131961      */
131962     plain: false,
131963
131964     /**
131965      * @cfg {String} itemCls
131966      * The class added to each child item of this TabPanel.
131967      */
131968     itemCls: 'x-tabpanel-child',
131969
131970     /**
131971      * @cfg {Number} minTabWidth
131972      * The minimum width for a tab in the {@link #tabBar}.
131973      */
131974     minTabWidth: undefined,
131975
131976     /**
131977      * @cfg {Number} maxTabWidth The maximum width for each tab.
131978      */
131979     maxTabWidth: undefined,
131980
131981     /**
131982      * @cfg {Boolean} deferredRender
131983      *
131984      * True by default to defer the rendering of child {@link Ext.container.Container#items items} to the browsers DOM
131985      * until a tab is activated. False will render all contained {@link Ext.container.Container#items items} as soon as
131986      * the {@link Ext.layout.container.Card layout} is rendered. If there is a significant amount of content or a lot of
131987      * heavy controls being rendered into panels that are not displayed by default, setting this to true might improve
131988      * performance.
131989      *
131990      * The deferredRender property is internally passed to the layout manager for TabPanels ({@link
131991      * Ext.layout.container.Card}) as its {@link Ext.layout.container.Card#deferredRender} configuration value.
131992      *
131993      * **Note**: leaving deferredRender as true means that the content within an unactivated tab will not be available
131994      */
131995     deferredRender : true,
131996
131997     //inherit docs
131998     initComponent: function() {
131999         var me = this,
132000             dockedItems = me.dockedItems || [],
132001             activeTab = me.activeTab || 0;
132002
132003         me.layout = Ext.create('Ext.layout.container.Card', Ext.apply({
132004             owner: me,
132005             deferredRender: me.deferredRender,
132006             itemCls: me.itemCls
132007         }, me.layout));
132008
132009         /**
132010          * @property {Ext.tab.Bar} tabBar Internal reference to the docked TabBar
132011          */
132012         me.tabBar = Ext.create('Ext.tab.Bar', Ext.apply({}, me.tabBar, {
132013             dock: me.tabPosition,
132014             plain: me.plain,
132015             border: me.border,
132016             cardLayout: me.layout,
132017             tabPanel: me
132018         }));
132019
132020         if (dockedItems && !Ext.isArray(dockedItems)) {
132021             dockedItems = [dockedItems];
132022         }
132023
132024         dockedItems.push(me.tabBar);
132025         me.dockedItems = dockedItems;
132026
132027         me.addEvents(
132028             /**
132029              * @event
132030              * Fires before a tab change (activated by {@link #setActiveTab}). Return false in any listener to cancel
132031              * the tabchange
132032              * @param {Ext.tab.Panel} tabPanel The TabPanel
132033              * @param {Ext.Component} newCard The card that is about to be activated
132034              * @param {Ext.Component} oldCard The card that is currently active
132035              */
132036             'beforetabchange',
132037
132038             /**
132039              * @event
132040              * Fires when a new tab has been activated (activated by {@link #setActiveTab}).
132041              * @param {Ext.tab.Panel} tabPanel The TabPanel
132042              * @param {Ext.Component} newCard The newly activated item
132043              * @param {Ext.Component} oldCard The previously active item
132044              */
132045             'tabchange'
132046         );
132047         me.callParent(arguments);
132048
132049         //set the active tab
132050         me.setActiveTab(activeTab);
132051         //set the active tab after initial layout
132052         me.on('afterlayout', me.afterInitialLayout, me, {single: true});
132053     },
132054
132055     /**
132056      * @private
132057      * We have to wait until after the initial layout to visually activate the activeTab (if set).
132058      * The active tab has different margins than normal tabs, so if the initial layout happens with
132059      * a tab active, its layout will be offset improperly due to the active margin style. Waiting
132060      * until after the initial layout avoids this issue.
132061      */
132062     afterInitialLayout: function() {
132063         var me = this,
132064             card = me.getComponent(me.activeTab);
132065
132066         if (card) {
132067             me.layout.setActiveItem(card);
132068         }
132069     },
132070
132071     /**
132072      * Makes the given card active. Makes it the visible card in the TabPanel's CardLayout and highlights the Tab.
132073      * @param {String/Number/Ext.Component} card The card to make active. Either an ID, index or the component itself.
132074      */
132075     setActiveTab: function(card) {
132076         var me = this,
132077             previous;
132078
132079         card = me.getComponent(card);
132080         if (card) {
132081             previous = me.getActiveTab();
132082
132083             if (previous && previous !== card && me.fireEvent('beforetabchange', me, card, previous) === false) {
132084                 return false;
132085             }
132086
132087             me.tabBar.setActiveTab(card.tab);
132088             me.activeTab = card;
132089             if (me.rendered) {
132090                 me.layout.setActiveItem(card);
132091             }
132092
132093             if (previous && previous !== card) {
132094                 me.fireEvent('tabchange', me, card, previous);
132095             }
132096         }
132097     },
132098
132099     /**
132100      * Returns the item that is currently active inside this TabPanel. Note that before the TabPanel first activates a
132101      * child component this will return whatever was configured in the {@link #activeTab} config option
132102      * @return {String/Number/Ext.Component} The currently active item
132103      */
132104     getActiveTab: function() {
132105         return this.activeTab;
132106     },
132107
132108     /**
132109      * Returns the {@link Ext.tab.Bar} currently used in this TabPanel
132110      * @return {Ext.tab.Bar} The TabBar
132111      */
132112     getTabBar: function() {
132113         return this.tabBar;
132114     },
132115
132116     /**
132117      * @ignore
132118      * Makes sure we have a Tab for each item added to the TabPanel
132119      */
132120     onAdd: function(item, index) {
132121         var me = this,
132122             cfg = item.tabConfig || {},
132123             defaultConfig = {
132124                 xtype: 'tab',
132125                 card: item,
132126                 disabled: item.disabled,
132127                 closable: item.closable,
132128                 hidden: item.hidden,
132129                 tabBar: me.tabBar
132130             };
132131
132132         if (item.closeText) {
132133             defaultConfig.closeText = item.closeText;
132134         }
132135         cfg = Ext.applyIf(cfg, defaultConfig);
132136         item.tab = me.tabBar.insert(index, cfg);
132137
132138         item.on({
132139             scope : me,
132140             enable: me.onItemEnable,
132141             disable: me.onItemDisable,
132142             beforeshow: me.onItemBeforeShow,
132143             iconchange: me.onItemIconChange,
132144             titlechange: me.onItemTitleChange
132145         });
132146
132147         if (item.isPanel) {
132148             if (me.removePanelHeader) {
132149                 item.preventHeader = true;
132150                 if (item.rendered) {
132151                     item.updateHeader();
132152                 }
132153             }
132154             if (item.isPanel && me.border) {
132155                 item.setBorder(false);
132156             }
132157         }
132158
132159         // ensure that there is at least one active tab
132160         if (this.rendered && me.items.getCount() === 1) {
132161             me.setActiveTab(0);
132162         }
132163     },
132164
132165     /**
132166      * @private
132167      * Enable corresponding tab when item is enabled.
132168      */
132169     onItemEnable: function(item){
132170         item.tab.enable();
132171     },
132172
132173     /**
132174      * @private
132175      * Disable corresponding tab when item is enabled.
132176      */
132177     onItemDisable: function(item){
132178         item.tab.disable();
132179     },
132180
132181     /**
132182      * @private
132183      * Sets activeTab before item is shown.
132184      */
132185     onItemBeforeShow: function(item) {
132186         if (item !== this.activeTab) {
132187             this.setActiveTab(item);
132188             return false;
132189         }
132190     },
132191
132192     /**
132193      * @private
132194      * Update the tab iconCls when panel iconCls has been set or changed.
132195      */
132196     onItemIconChange: function(item, newIconCls) {
132197         item.tab.setIconCls(newIconCls);
132198         this.getTabBar().doLayout();
132199     },
132200
132201     /**
132202      * @private
132203      * Update the tab title when panel title has been set or changed.
132204      */
132205     onItemTitleChange: function(item, newTitle) {
132206         item.tab.setText(newTitle);
132207         this.getTabBar().doLayout();
132208     },
132209
132210
132211     /**
132212      * @ignore
132213      * If we're removing the currently active tab, activate the nearest one. The item is removed when we call super,
132214      * so we can do preprocessing before then to find the card's index
132215      */
132216     doRemove: function(item, autoDestroy) {
132217         var me = this,
132218             items = me.items,
132219             // At this point the item hasn't been removed from the items collection.
132220             // As such, if we want to check if there are no more tabs left, we have to
132221             // check for one, as opposed to 0.
132222             hasItemsLeft = items.getCount() > 1;
132223
132224         if (me.destroying || !hasItemsLeft) {
132225             me.activeTab = null;
132226         } else if (item === me.activeTab) {
132227              me.setActiveTab(item.next() || items.getAt(0));
132228         }
132229         me.callParent(arguments);
132230
132231         // Remove the two references
132232         delete item.tab.card;
132233         delete item.tab;
132234     },
132235
132236     /**
132237      * @ignore
132238      * Makes sure we remove the corresponding Tab when an item is removed
132239      */
132240     onRemove: function(item, autoDestroy) {
132241         var me = this;
132242
132243         item.un({
132244             scope : me,
132245             enable: me.onItemEnable,
132246             disable: me.onItemDisable,
132247             beforeshow: me.onItemBeforeShow
132248         });
132249         if (!me.destroying && item.tab.ownerCt == me.tabBar) {
132250             me.tabBar.remove(item.tab);
132251         }
132252     }
132253 });
132254
132255 /**
132256  * A simple element that adds extra horizontal space between items in a toolbar.
132257  * By default a 2px wide space is added via CSS specification:
132258  *
132259  *     .x-toolbar .x-toolbar-spacer {
132260  *         width: 2px;
132261  *     }
132262  *
132263  * Example:
132264  *
132265  *     @example
132266  *     Ext.create('Ext.panel.Panel', {
132267  *         title: 'Toolbar Spacer Example',
132268  *         width: 300,
132269  *         height: 200,
132270  *         tbar : [
132271  *             'Item 1',
132272  *             { xtype: 'tbspacer' }, // or ' '
132273  *             'Item 2',
132274  *             // space width is also configurable via javascript
132275  *             { xtype: 'tbspacer', width: 50 }, // add a 50px space
132276  *             'Item 3'
132277  *         ],
132278  *         renderTo: Ext.getBody()
132279  *     });
132280  */
132281 Ext.define('Ext.toolbar.Spacer', {
132282     extend: 'Ext.Component',
132283     alias: 'widget.tbspacer',
132284     alternateClassName: 'Ext.Toolbar.Spacer',
132285     baseCls: Ext.baseCSSPrefix + 'toolbar-spacer',
132286     focusable: false
132287 });
132288 /**
132289  * @class Ext.tree.Column
132290  * @extends Ext.grid.column.Column
132291  * 
132292  * Provides indentation and folder structure markup for a Tree taking into account
132293  * depth and position within the tree hierarchy.
132294  * 
132295  * @private
132296  */
132297 Ext.define('Ext.tree.Column', {
132298     extend: 'Ext.grid.column.Column',
132299     alias: 'widget.treecolumn',
132300
132301     initComponent: function() {
132302         var origRenderer = this.renderer || this.defaultRenderer,
132303             origScope    = this.scope || window;
132304
132305         this.renderer = function(value, metaData, record, rowIdx, colIdx, store, view) {
132306             var buf   = [],
132307                 format = Ext.String.format,
132308                 depth = record.getDepth(),
132309                 treePrefix  = Ext.baseCSSPrefix + 'tree-',
132310                 elbowPrefix = treePrefix + 'elbow-',
132311                 expanderCls = treePrefix + 'expander',
132312                 imgText     = '<img src="{1}" class="{0}" />',
132313                 checkboxText= '<input type="button" role="checkbox" class="{0}" {1} />',
132314                 formattedValue = origRenderer.apply(origScope, arguments),
132315                 href = record.get('href'),
132316                 target = record.get('hrefTarget'),
132317                 cls = record.get('cls');
132318
132319             while (record) {
132320                 if (!record.isRoot() || (record.isRoot() && view.rootVisible)) {
132321                     if (record.getDepth() === depth) {
132322                         buf.unshift(format(imgText,
132323                             treePrefix + 'icon ' + 
132324                             treePrefix + 'icon' + (record.get('icon') ? '-inline ' : (record.isLeaf() ? '-leaf ' : '-parent ')) +
132325                             (record.get('iconCls') || ''),
132326                             record.get('icon') || Ext.BLANK_IMAGE_URL
132327                         ));
132328                         if (record.get('checked') !== null) {
132329                             buf.unshift(format(
132330                                 checkboxText,
132331                                 (treePrefix + 'checkbox') + (record.get('checked') ? ' ' + treePrefix + 'checkbox-checked' : ''),
132332                                 record.get('checked') ? 'aria-checked="true"' : ''
132333                             ));
132334                             if (record.get('checked')) {
132335                                 metaData.tdCls += (' ' + treePrefix + 'checked');
132336                             }
132337                         }
132338                         if (record.isLast()) {
132339                             if (record.isExpandable()) {
132340                                 buf.unshift(format(imgText, (elbowPrefix + 'end-plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
132341                             } else {
132342                                 buf.unshift(format(imgText, (elbowPrefix + 'end'), Ext.BLANK_IMAGE_URL));
132343                             }
132344                             
132345                         } else {
132346                             if (record.isExpandable()) {
132347                                 buf.unshift(format(imgText, (elbowPrefix + 'plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
132348                             } else {
132349                                 buf.unshift(format(imgText, (treePrefix + 'elbow'), Ext.BLANK_IMAGE_URL));
132350                             }
132351                         }
132352                     } else {
132353                         if (record.isLast() || record.getDepth() === 0) {
132354                             buf.unshift(format(imgText, (elbowPrefix + 'empty'), Ext.BLANK_IMAGE_URL));
132355                         } else if (record.getDepth() !== 0) {
132356                             buf.unshift(format(imgText, (elbowPrefix + 'line'), Ext.BLANK_IMAGE_URL));
132357                         }                      
132358                     }
132359                 }
132360                 record = record.parentNode;
132361             }
132362             if (href) {
132363                 buf.push('<a href="', href, '" target="', target, '">', formattedValue, '</a>');
132364             } else {
132365                 buf.push(formattedValue);
132366             }
132367             if (cls) {
132368                 metaData.tdCls += ' ' + cls;
132369             }
132370             return buf.join('');
132371         };
132372         this.callParent(arguments);
132373     },
132374
132375     defaultRenderer: function(value) {
132376         return value;
132377     }
132378 });
132379 /**
132380  * Used as a view by {@link Ext.tree.Panel TreePanel}.
132381  */
132382 Ext.define('Ext.tree.View', {
132383     extend: 'Ext.view.Table',
132384     alias: 'widget.treeview',
132385
132386     loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
132387     expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
132388
132389     expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
132390     checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
132391     expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
132392
132393     // Class to add to the node wrap element used to hold nodes when a parent is being
132394     // collapsed or expanded. During the animation, UI interaction is forbidden by testing
132395     // for an ancestor node with this class.
132396     nodeAnimWrapCls: Ext.baseCSSPrefix + 'tree-animator-wrap',
132397
132398     blockRefresh: true,
132399
132400     /** 
132401      * @cfg {Boolean} rootVisible
132402      * False to hide the root node.
132403      */
132404     rootVisible: true,
132405
132406     /** 
132407      * @cfg {Boolean} animate
132408      * True to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
132409      */
132410
132411     expandDuration: 250,
132412     collapseDuration: 250,
132413     
132414     toggleOnDblClick: true,
132415
132416     initComponent: function() {
132417         var me = this;
132418         
132419         if (me.initialConfig.animate === undefined) {
132420             me.animate = Ext.enableFx;
132421         }
132422         
132423         me.store = Ext.create('Ext.data.NodeStore', {
132424             recursive: true,
132425             rootVisible: me.rootVisible,
132426             listeners: {
132427                 beforeexpand: me.onBeforeExpand,
132428                 expand: me.onExpand,
132429                 beforecollapse: me.onBeforeCollapse,
132430                 collapse: me.onCollapse,
132431                 scope: me
132432             }
132433         });
132434         
132435         if (me.node) {
132436             me.setRootNode(me.node);
132437         }
132438         me.animQueue = {};
132439         me.callParent(arguments);
132440     },
132441
132442     processUIEvent: function(e) {
132443         // If the clicked node is part of an animation, ignore the click.
132444         // This is because during a collapse animation, the associated Records
132445         // will already have been removed from the Store, and the event is not processable.
132446         if (e.getTarget('.' + this.nodeAnimWrapCls, this.el)) {
132447             return false;
132448         }
132449         return this.callParent(arguments);
132450     },
132451
132452     onClear: function(){
132453         this.store.removeAll();    
132454     },
132455
132456     setRootNode: function(node) {
132457         var me = this;        
132458         me.store.setNode(node);
132459         me.node = node;
132460         if (!me.rootVisible) {
132461             node.expand();
132462         }
132463     },
132464     
132465     onRender: function() {
132466         var me = this,
132467             el;
132468
132469         me.callParent(arguments);
132470
132471         el = me.el;
132472         el.on({
132473             scope: me,
132474             delegate: me.expanderSelector,
132475             mouseover: me.onExpanderMouseOver,
132476             mouseout: me.onExpanderMouseOut
132477         });
132478         el.on({
132479             scope: me,
132480             delegate: me.checkboxSelector,
132481             click: me.onCheckboxChange
132482         });
132483     },
132484
132485     onCheckboxChange: function(e, t) {
132486         var me = this,
132487             item = e.getTarget(me.getItemSelector(), me.getTargetEl());
132488             
132489         if (item) {
132490             me.onCheckChange(me.getRecord(item));
132491         }
132492     },
132493     
132494     onCheckChange: function(record){
132495         var checked = record.get('checked');
132496         if (Ext.isBoolean(checked)) {
132497             checked = !checked;
132498             record.set('checked', checked);
132499             this.fireEvent('checkchange', record, checked);
132500         }
132501     },
132502
132503     getChecked: function() {
132504         var checked = [];
132505         this.node.cascadeBy(function(rec){
132506             if (rec.get('checked')) {
132507                 checked.push(rec);
132508             }
132509         });
132510         return checked;
132511     },
132512     
132513     isItemChecked: function(rec){
132514         return rec.get('checked');
132515     },
132516
132517     createAnimWrap: function(record, index) {
132518         var thHtml = '',
132519             headerCt = this.panel.headerCt,
132520             headers = headerCt.getGridColumns(),
132521             i = 0, len = headers.length, item,
132522             node = this.getNode(record),
132523             tmpEl, nodeEl;
132524
132525         for (; i < len; i++) {
132526             item = headers[i];
132527             thHtml += '<th style="width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;"></th>';
132528         }
132529
132530         nodeEl = Ext.get(node);        
132531         tmpEl = nodeEl.insertSibling({
132532             tag: 'tr',
132533             html: [
132534                 '<td colspan="' + headerCt.getColumnCount() + '">',
132535                     '<div class="' + this.nodeAnimWrapCls + '">',
132536                         '<table class="' + Ext.baseCSSPrefix + 'grid-table" style="width: ' + headerCt.getFullWidth() + 'px;"><tbody>',
132537                             thHtml,
132538                         '</tbody></table>',
132539                     '</div>',
132540                 '</td>'
132541             ].join('')
132542         }, 'after');
132543
132544         return {
132545             record: record,
132546             node: node,
132547             el: tmpEl,
132548             expanding: false,
132549             collapsing: false,
132550             animating: false,
132551             animateEl: tmpEl.down('div'),
132552             targetEl: tmpEl.down('tbody')
132553         };
132554     },
132555
132556     getAnimWrap: function(parent) {
132557         if (!this.animate) {
132558             return null;
132559         }
132560
132561         // We are checking to see which parent is having the animation wrap
132562         while (parent) {
132563             if (parent.animWrap) {
132564                 return parent.animWrap;
132565             }
132566             parent = parent.parentNode;
132567         }
132568         return null;
132569     },
132570
132571     doAdd: function(nodes, records, index) {
132572         // If we are adding records which have a parent that is currently expanding
132573         // lets add them to the animation wrap
132574         var me = this,
132575             record = records[0],
132576             parent = record.parentNode,
132577             a = me.all.elements,
132578             relativeIndex = 0,
132579             animWrap = me.getAnimWrap(parent),
132580             targetEl, children, len;
132581
132582         if (!animWrap || !animWrap.expanding) {
132583             me.resetScrollers();
132584             return me.callParent(arguments);
132585         }
132586
132587         // We need the parent that has the animWrap, not the nodes parent
132588         parent = animWrap.record;
132589         
132590         // If there is an anim wrap we do our special magic logic
132591         targetEl = animWrap.targetEl;
132592         children = targetEl.dom.childNodes;
132593         
132594         // We subtract 1 from the childrens length because we have a tr in there with the th'es
132595         len = children.length - 1;
132596         
132597         // The relative index is the index in the full flat collection minus the index of the wraps parent
132598         relativeIndex = index - me.indexOf(parent) - 1;
132599         
132600         // If we are adding records to the wrap that have a higher relative index then there are currently children
132601         // it means we have to append the nodes to the wrap
132602         if (!len || relativeIndex >= len) {
132603             targetEl.appendChild(nodes);
132604         }
132605         // If there are already more children then the relative index it means we are adding child nodes of
132606         // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
132607         else {
132608             // +1 because of the tr with th'es that is already there
132609             Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
132610         }
132611
132612         // We also have to update the CompositeElementLite collection of the DataView
132613         Ext.Array.insert(a, index, nodes);
132614         
132615         // If we were in an animation we need to now change the animation
132616         // because the targetEl just got higher.
132617         if (animWrap.isAnimating) {
132618             me.onExpand(parent);
132619         }
132620     },
132621     
132622     beginBulkUpdate: function(){
132623         this.bulkUpdate = true;
132624         this.ownerCt.changingScrollbars = true;  
132625     },
132626     
132627     endBulkUpdate: function(){
132628         var me = this,
132629             ownerCt = me.ownerCt;
132630         
132631         me.bulkUpdate = false;
132632         me.ownerCt.changingScrollbars = true;  
132633         me.resetScrollers();  
132634     },
132635     
132636     onRemove : function(ds, record, index) {
132637         var me = this,
132638             bulk = me.bulkUpdate;
132639
132640         me.doRemove(record, index);
132641         if (!bulk) {
132642             me.updateIndexes(index);
132643         }
132644         if (me.store.getCount() === 0){
132645             me.refresh();
132646         }
132647         if (!bulk) {
132648             me.fireEvent('itemremove', record, index);
132649         }
132650     },
132651     
132652     doRemove: function(record, index) {
132653         // If we are adding records which have a parent that is currently expanding
132654         // lets add them to the animation wrap
132655         var me = this,
132656             parent = record.parentNode,
132657             all = me.all,
132658             animWrap = me.getAnimWrap(record),
132659             node = all.item(index).dom;
132660
132661         if (!animWrap || !animWrap.collapsing) {
132662             me.resetScrollers();
132663             return me.callParent(arguments);
132664         }
132665
132666         animWrap.targetEl.appendChild(node);
132667         all.removeElement(index);
132668     },
132669
132670     onBeforeExpand: function(parent, records, index) {
132671         var me = this,
132672             animWrap;
132673             
132674         if (!me.rendered || !me.animate) {
132675             return;
132676         }
132677
132678         if (me.getNode(parent)) {
132679             animWrap = me.getAnimWrap(parent);
132680             if (!animWrap) {
132681                 animWrap = parent.animWrap = me.createAnimWrap(parent);
132682                 animWrap.animateEl.setHeight(0);
132683             }
132684             else if (animWrap.collapsing) {
132685                 // If we expand this node while it is still expanding then we
132686                 // have to remove the nodes from the animWrap.
132687                 animWrap.targetEl.select(me.itemSelector).remove();
132688             } 
132689             animWrap.expanding = true;
132690             animWrap.collapsing = false;
132691         }
132692     },
132693
132694     onExpand: function(parent) {
132695         var me = this,
132696             queue = me.animQueue,
132697             id = parent.getId(),
132698             animWrap,
132699             animateEl, 
132700             targetEl,
132701             queueItem;        
132702         
132703         if (me.singleExpand) {
132704             me.ensureSingleExpand(parent);
132705         }
132706         
132707         animWrap = me.getAnimWrap(parent);
132708
132709         if (!animWrap) {
132710             me.resetScrollers();
132711             return;
132712         }
132713         
132714         animateEl = animWrap.animateEl;
132715         targetEl = animWrap.targetEl;
132716
132717         animateEl.stopAnimation();
132718         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
132719         queue[id] = true;
132720         animateEl.slideIn('t', {
132721             duration: me.expandDuration,
132722             listeners: {
132723                 scope: me,
132724                 lastframe: function() {
132725                     // Move all the nodes out of the anim wrap to their proper location
132726                     animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
132727                     animWrap.el.remove();
132728                     me.resetScrollers();
132729                     delete animWrap.record.animWrap;
132730                     delete queue[id];
132731                 }
132732             }
132733         });
132734         
132735         animWrap.isAnimating = true;
132736     },
132737     
132738     resetScrollers: function(){
132739         if (!this.bulkUpdate) {
132740             var panel = this.panel;
132741             
132742             panel.determineScrollbars();
132743             panel.invalidateScroller();
132744         }
132745     },
132746
132747     onBeforeCollapse: function(parent, records, index) {
132748         var me = this,
132749             animWrap;
132750             
132751         if (!me.rendered || !me.animate) {
132752             return;
132753         }
132754
132755         if (me.getNode(parent)) {
132756             animWrap = me.getAnimWrap(parent);
132757             if (!animWrap) {
132758                 animWrap = parent.animWrap = me.createAnimWrap(parent, index);
132759             }
132760             else if (animWrap.expanding) {
132761                 // If we collapse this node while it is still expanding then we
132762                 // have to remove the nodes from the animWrap.
132763                 animWrap.targetEl.select(this.itemSelector).remove();
132764             }
132765             animWrap.expanding = false;
132766             animWrap.collapsing = true;
132767         }
132768     },
132769     
132770     onCollapse: function(parent) {
132771         var me = this,
132772             queue = me.animQueue,
132773             id = parent.getId(),
132774             animWrap = me.getAnimWrap(parent),
132775             animateEl, targetEl;
132776
132777         if (!animWrap) {
132778             me.resetScrollers();
132779             return;
132780         }
132781         
132782         animateEl = animWrap.animateEl;
132783         targetEl = animWrap.targetEl;
132784
132785         queue[id] = true;
132786         
132787         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
132788         animateEl.stopAnimation();
132789         animateEl.slideOut('t', {
132790             duration: me.collapseDuration,
132791             listeners: {
132792                 scope: me,
132793                 lastframe: function() {
132794                     animWrap.el.remove();
132795                     delete animWrap.record.animWrap;
132796                     me.resetScrollers();
132797                     delete queue[id];
132798                 }             
132799             }
132800         });
132801         animWrap.isAnimating = true;
132802     },
132803     
132804     /**
132805      * Checks if a node is currently undergoing animation
132806      * @private
132807      * @param {Ext.data.Model} node The node
132808      * @return {Boolean} True if the node is animating
132809      */
132810     isAnimating: function(node) {
132811         return !!this.animQueue[node.getId()];    
132812     },
132813     
132814     collectData: function(records) {
132815         var data = this.callParent(arguments),
132816             rows = data.rows,
132817             len = rows.length,
132818             i = 0,
132819             row, record;
132820             
132821         for (; i < len; i++) {
132822             row = rows[i];
132823             record = records[i];
132824             if (record.get('qtip')) {
132825                 row.rowAttr = 'data-qtip="' + record.get('qtip') + '"';
132826                 if (record.get('qtitle')) {
132827                     row.rowAttr += ' ' + 'data-qtitle="' + record.get('qtitle') + '"';
132828                 }
132829             }
132830             if (record.isExpanded()) {
132831                 row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
132832             }
132833             if (record.isLoading()) {
132834                 row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
132835             }
132836         }
132837         
132838         return data;
132839     },
132840     
132841     /**
132842      * Expands a record that is loaded in the view.
132843      * @param {Ext.data.Model} record The record to expand
132844      * @param {Boolean} deep (optional) True to expand nodes all the way down the tree hierarchy.
132845      * @param {Function} callback (optional) The function to run after the expand is completed
132846      * @param {Object} scope (optional) The scope of the callback function.
132847      */
132848     expand: function(record, deep, callback, scope) {
132849         return record.expand(deep, callback, scope);
132850     },
132851     
132852     /**
132853      * Collapses a record that is loaded in the view.
132854      * @param {Ext.data.Model} record The record to collapse
132855      * @param {Boolean} deep (optional) True to collapse nodes all the way up the tree hierarchy.
132856      * @param {Function} callback (optional) The function to run after the collapse is completed
132857      * @param {Object} scope (optional) The scope of the callback function.
132858      */
132859     collapse: function(record, deep, callback, scope) {
132860         return record.collapse(deep, callback, scope);
132861     },
132862     
132863     /**
132864      * Toggles a record between expanded and collapsed.
132865      * @param {Ext.data.Model} recordInstance
132866      */
132867     toggle: function(record) {
132868         this[record.isExpanded() ? 'collapse' : 'expand'](record);
132869     },
132870     
132871     onItemDblClick: function(record, item, index) {
132872         this.callParent(arguments);
132873         if (this.toggleOnDblClick) {
132874             this.toggle(record);
132875         }
132876     },
132877     
132878     onBeforeItemMouseDown: function(record, item, index, e) {
132879         if (e.getTarget(this.expanderSelector, item)) {
132880             return false;
132881         }
132882         return this.callParent(arguments);
132883     },
132884     
132885     onItemClick: function(record, item, index, e) {
132886         if (e.getTarget(this.expanderSelector, item)) {
132887             this.toggle(record);
132888             return false;
132889         }
132890         return this.callParent(arguments);
132891     },
132892     
132893     onExpanderMouseOver: function(e, t) {
132894         e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
132895     },
132896     
132897     onExpanderMouseOut: function(e, t) {
132898         e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
132899     },
132900     
132901     /**
132902      * Gets the base TreeStore from the bound TreePanel.
132903      */
132904     getTreeStore: function() {
132905         return this.panel.store;
132906     },    
132907     
132908     ensureSingleExpand: function(node) {
132909         var parent = node.parentNode;
132910         if (parent) {
132911             parent.eachChild(function(child) {
132912                 if (child !== node && child.isExpanded()) {
132913                     child.collapse();
132914                 }
132915             });
132916         }
132917     }
132918 });
132919 /**
132920  * The TreePanel provides tree-structured UI representation of tree-structured data.
132921  * A TreePanel must be bound to a {@link Ext.data.TreeStore}. TreePanel's support
132922  * multiple columns through the {@link #columns} configuration.
132923  *
132924  * Simple TreePanel using inline data:
132925  *
132926  *     @example
132927  *     var store = Ext.create('Ext.data.TreeStore', {
132928  *         root: {
132929  *             expanded: true,
132930  *             children: [
132931  *                 { text: "detention", leaf: true },
132932  *                 { text: "homework", expanded: true, children: [
132933  *                     { text: "book report", leaf: true },
132934  *                     { text: "alegrbra", leaf: true}
132935  *                 ] },
132936  *                 { text: "buy lottery tickets", leaf: true }
132937  *             ]
132938  *         }
132939  *     });
132940  *
132941  *     Ext.create('Ext.tree.Panel', {
132942  *         title: 'Simple Tree',
132943  *         width: 200,
132944  *         height: 150,
132945  *         store: store,
132946  *         rootVisible: false,
132947  *         renderTo: Ext.getBody()
132948  *     });
132949  *
132950  * For the tree node config options (like `text`, `leaf`, `expanded`), see the documentation of
132951  * {@link Ext.data.NodeInterface NodeInterface} config options.
132952  */
132953 Ext.define('Ext.tree.Panel', {
132954     extend: 'Ext.panel.Table',
132955     alias: 'widget.treepanel',
132956     alternateClassName: ['Ext.tree.TreePanel', 'Ext.TreePanel'],
132957     requires: ['Ext.tree.View', 'Ext.selection.TreeModel', 'Ext.tree.Column'],
132958     viewType: 'treeview',
132959     selType: 'treemodel',
132960
132961     treeCls: Ext.baseCSSPrefix + 'tree-panel',
132962
132963     deferRowRender: false,
132964
132965     /**
132966      * @cfg {Boolean} lines False to disable tree lines.
132967      */
132968     lines: true,
132969
132970     /**
132971      * @cfg {Boolean} useArrows True to use Vista-style arrows in the tree.
132972      */
132973     useArrows: false,
132974
132975     /**
132976      * @cfg {Boolean} singleExpand True if only 1 node per branch may be expanded.
132977      */
132978     singleExpand: false,
132979
132980     ddConfig: {
132981         enableDrag: true,
132982         enableDrop: true
132983     },
132984
132985     /**
132986      * @cfg {Boolean} animate True to enable animated expand/collapse. Defaults to the value of {@link Ext#enableFx}.
132987      */
132988
132989     /**
132990      * @cfg {Boolean} rootVisible False to hide the root node.
132991      */
132992     rootVisible: true,
132993
132994     /**
132995      * @cfg {Boolean} displayField The field inside the model that will be used as the node's text.
132996      */
132997     displayField: 'text',
132998
132999     /**
133000      * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
133001      * Allows you to not specify a store on this TreePanel. This is useful for creating a simple tree with preloaded
133002      * data without having to specify a TreeStore and Model. A store and model will be created and root will be passed
133003      * to that store. For example:
133004      *
133005      *     Ext.create('Ext.tree.Panel', {
133006      *         title: 'Simple Tree',
133007      *         root: {
133008      *             text: "Root node",
133009      *             expanded: true,
133010      *             children: [
133011      *                 { text: "Child 1", leaf: true },
133012      *                 { text: "Child 2", leaf: true }
133013      *             ]
133014      *         },
133015      *         renderTo: Ext.getBody()
133016      *     });
133017      */
133018     root: null,
133019
133020     // Required for the Lockable Mixin. These are the configurations which will be copied to the
133021     // normal and locked sub tablepanels
133022     normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'],
133023     lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'],
133024
133025     /**
133026      * @cfg {Boolean} hideHeaders True to hide the headers. Defaults to `undefined`.
133027      */
133028
133029     /**
133030      * @cfg {Boolean} folderSort True to automatically prepend a leaf sorter to the store. Defaults to `undefined`.
133031      */
133032
133033     constructor: function(config) {
133034         config = config || {};
133035         if (config.animate === undefined) {
133036             config.animate = Ext.enableFx;
133037         }
133038         this.enableAnimations = config.animate;
133039         delete config.animate;
133040
133041         this.callParent([config]);
133042     },
133043
133044     initComponent: function() {
133045         var me = this,
133046             cls = [me.treeCls];
133047
133048         if (me.useArrows) {
133049             cls.push(Ext.baseCSSPrefix + 'tree-arrows');
133050             me.lines = false;
133051         }
133052
133053         if (me.lines) {
133054             cls.push(Ext.baseCSSPrefix + 'tree-lines');
133055         } else if (!me.useArrows) {
133056             cls.push(Ext.baseCSSPrefix + 'tree-no-lines');
133057         }
133058
133059         if (Ext.isString(me.store)) {
133060             me.store = Ext.StoreMgr.lookup(me.store);
133061         } else if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
133062             me.store = Ext.create('Ext.data.TreeStore', Ext.apply({}, me.store || {}, {
133063                 root: me.root,
133064                 fields: me.fields,
133065                 model: me.model,
133066                 folderSort: me.folderSort
133067             }));
133068         } else if (me.root) {
133069             me.store = Ext.data.StoreManager.lookup(me.store);
133070             me.store.setRootNode(me.root);
133071             if (me.folderSort !== undefined) {
133072                 me.store.folderSort = me.folderSort;
133073                 me.store.sort();
133074             }
133075         }
133076
133077         // I'm not sure if we want to this. It might be confusing
133078         // if (me.initialConfig.rootVisible === undefined && !me.getRootNode()) {
133079         //     me.rootVisible = false;
133080         // }
133081
133082         me.viewConfig = Ext.applyIf(me.viewConfig || {}, {
133083             rootVisible: me.rootVisible,
133084             animate: me.enableAnimations,
133085             singleExpand: me.singleExpand,
133086             node: me.store.getRootNode(),
133087             hideHeaders: me.hideHeaders
133088         });
133089
133090         me.mon(me.store, {
133091             scope: me,
133092             rootchange: me.onRootChange,
133093             clear: me.onClear
133094         });
133095
133096         me.relayEvents(me.store, [
133097             /**
133098              * @event beforeload
133099              * @alias Ext.data.Store#beforeload
133100              */
133101             'beforeload',
133102
133103             /**
133104              * @event load
133105              * @alias Ext.data.Store#load
133106              */
133107             'load'
133108         ]);
133109
133110         me.store.on({
133111             /**
133112              * @event itemappend
133113              * @alias Ext.data.TreeStore#append
133114              */
133115             append: me.createRelayer('itemappend'),
133116
133117             /**
133118              * @event itemremove
133119              * @alias Ext.data.TreeStore#remove
133120              */
133121             remove: me.createRelayer('itemremove'),
133122
133123             /**
133124              * @event itemmove
133125              * @alias Ext.data.TreeStore#move
133126              */
133127             move: me.createRelayer('itemmove'),
133128
133129             /**
133130              * @event iteminsert
133131              * @alias Ext.data.TreeStore#insert
133132              */
133133             insert: me.createRelayer('iteminsert'),
133134
133135             /**
133136              * @event beforeitemappend
133137              * @alias Ext.data.TreeStore#beforeappend
133138              */
133139             beforeappend: me.createRelayer('beforeitemappend'),
133140
133141             /**
133142              * @event beforeitemremove
133143              * @alias Ext.data.TreeStore#beforeremove
133144              */
133145             beforeremove: me.createRelayer('beforeitemremove'),
133146
133147             /**
133148              * @event beforeitemmove
133149              * @alias Ext.data.TreeStore#beforemove
133150              */
133151             beforemove: me.createRelayer('beforeitemmove'),
133152
133153             /**
133154              * @event beforeiteminsert
133155              * @alias Ext.data.TreeStore#beforeinsert
133156              */
133157             beforeinsert: me.createRelayer('beforeiteminsert'),
133158
133159             /**
133160              * @event itemexpand
133161              * @alias Ext.data.TreeStore#expand
133162              */
133163             expand: me.createRelayer('itemexpand'),
133164
133165             /**
133166              * @event itemcollapse
133167              * @alias Ext.data.TreeStore#collapse
133168              */
133169             collapse: me.createRelayer('itemcollapse'),
133170
133171             /**
133172              * @event beforeitemexpand
133173              * @alias Ext.data.TreeStore#beforeexpand
133174              */
133175             beforeexpand: me.createRelayer('beforeitemexpand'),
133176
133177             /**
133178              * @event beforeitemcollapse
133179              * @alias Ext.data.TreeStore#beforecollapse
133180              */
133181             beforecollapse: me.createRelayer('beforeitemcollapse')
133182         });
133183
133184         // If the user specifies the headers collection manually then dont inject our own
133185         if (!me.columns) {
133186             if (me.initialConfig.hideHeaders === undefined) {
133187                 me.hideHeaders = true;
133188             }
133189             me.columns = [{
133190                 xtype    : 'treecolumn',
133191                 text     : 'Name',
133192                 flex     : 1,
133193                 dataIndex: me.displayField
133194             }];
133195         }
133196
133197         if (me.cls) {
133198             cls.push(me.cls);
133199         }
133200         me.cls = cls.join(' ');
133201         me.callParent();
133202
133203         me.relayEvents(me.getView(), [
133204             /**
133205              * @event checkchange
133206              * Fires when a node with a checkbox's checked property changes
133207              * @param {Ext.data.Model} node The node who's checked property was changed
133208              * @param {Boolean} checked The node's new checked state
133209              */
133210             'checkchange'
133211         ]);
133212
133213         // If the root is not visible and there is no rootnode defined, then just lets load the store
133214         if (!me.getView().rootVisible && !me.getRootNode()) {
133215             me.setRootNode({
133216                 expanded: true
133217             });
133218         }
133219     },
133220
133221     onClear: function(){
133222         this.view.onClear();
133223     },
133224
133225     /**
133226      * Sets root node of this tree.
133227      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
133228      * @return {Ext.data.NodeInterface} The new root
133229      */
133230     setRootNode: function() {
133231         return this.store.setRootNode.apply(this.store, arguments);
133232     },
133233
133234     /**
133235      * Returns the root node for this tree.
133236      * @return {Ext.data.NodeInterface}
133237      */
133238     getRootNode: function() {
133239         return this.store.getRootNode();
133240     },
133241
133242     onRootChange: function(root) {
133243         this.view.setRootNode(root);
133244     },
133245
133246     /**
133247      * Retrieve an array of checked records.
133248      * @return {Ext.data.Model[]} An array containing the checked records
133249      */
133250     getChecked: function() {
133251         return this.getView().getChecked();
133252     },
133253
133254     isItemChecked: function(rec) {
133255         return rec.get('checked');
133256     },
133257
133258     /**
133259      * Expand all nodes
133260      * @param {Function} callback (optional) A function to execute when the expand finishes.
133261      * @param {Object} scope (optional) The scope of the callback function
133262      */
133263     expandAll : function(callback, scope) {
133264         var root = this.getRootNode(),
133265             animate = this.enableAnimations,
133266             view = this.getView();
133267         if (root) {
133268             if (!animate) {
133269                 view.beginBulkUpdate();
133270             }
133271             root.expand(true, callback, scope);
133272             if (!animate) {
133273                 view.endBulkUpdate();
133274             }
133275         }
133276     },
133277
133278     /**
133279      * Collapse all nodes
133280      * @param {Function} callback (optional) A function to execute when the collapse finishes.
133281      * @param {Object} scope (optional) The scope of the callback function
133282      */
133283     collapseAll : function(callback, scope) {
133284         var root = this.getRootNode(),
133285             animate = this.enableAnimations,
133286             view = this.getView();
133287
133288         if (root) {
133289             if (!animate) {
133290                 view.beginBulkUpdate();
133291             }
133292             if (view.rootVisible) {
133293                 root.collapse(true, callback, scope);
133294             } else {
133295                 root.collapseChildren(true, callback, scope);
133296             }
133297             if (!animate) {
133298                 view.endBulkUpdate();
133299             }
133300         }
133301     },
133302
133303     /**
133304      * Expand the tree to the path of a particular node.
133305      * @param {String} path The path to expand. The path should include a leading separator.
133306      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
133307      * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
133308      * @param {Function} callback (optional) A function to execute when the expand finishes. The callback will be called with
133309      * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
133310      * @param {Object} scope (optional) The scope of the callback function
133311      */
133312     expandPath: function(path, field, separator, callback, scope) {
133313         var me = this,
133314             current = me.getRootNode(),
133315             index = 1,
133316             view = me.getView(),
133317             keys,
133318             expander;
133319
133320         field = field || me.getRootNode().idProperty;
133321         separator = separator || '/';
133322
133323         if (Ext.isEmpty(path)) {
133324             Ext.callback(callback, scope || me, [false, null]);
133325             return;
133326         }
133327
133328         keys = path.split(separator);
133329         if (current.get(field) != keys[1]) {
133330             // invalid root
133331             Ext.callback(callback, scope || me, [false, current]);
133332             return;
133333         }
133334
133335         expander = function(){
133336             if (++index === keys.length) {
133337                 Ext.callback(callback, scope || me, [true, current]);
133338                 return;
133339             }
133340             var node = current.findChild(field, keys[index]);
133341             if (!node) {
133342                 Ext.callback(callback, scope || me, [false, current]);
133343                 return;
133344             }
133345             current = node;
133346             current.expand(false, expander);
133347         };
133348         current.expand(false, expander);
133349     },
133350
133351     /**
133352      * Expand the tree to the path of a particular node, then select it.
133353      * @param {String} path The path to select. The path should include a leading separator.
133354      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
133355      * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
133356      * @param {Function} callback (optional) A function to execute when the select finishes. The callback will be called with
133357      * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
133358      * @param {Object} scope (optional) The scope of the callback function
133359      */
133360     selectPath: function(path, field, separator, callback, scope) {
133361         var me = this,
133362             keys,
133363             last;
133364
133365         field = field || me.getRootNode().idProperty;
133366         separator = separator || '/';
133367
133368         keys = path.split(separator);
133369         last = keys.pop();
133370
133371         me.expandPath(keys.join(separator), field, separator, function(success, node){
133372             var doSuccess = false;
133373             if (success && node) {
133374                 node = node.findChild(field, last);
133375                 if (node) {
133376                     me.getSelectionModel().select(node);
133377                     Ext.callback(callback, scope || me, [true, node]);
133378                     doSuccess = true;
133379                 }
133380             } else if (node === me.getRootNode()) {
133381                 doSuccess = true;
133382             }
133383             Ext.callback(callback, scope || me, [doSuccess, node]);
133384         }, me);
133385     }
133386 });
133387
133388 /**
133389  * @class Ext.view.DragZone
133390  * @extends Ext.dd.DragZone
133391  * @private
133392  */
133393 Ext.define('Ext.view.DragZone', {
133394     extend: 'Ext.dd.DragZone',
133395     containerScroll: false,
133396
133397     constructor: function(config) {
133398         var me = this;
133399
133400         Ext.apply(me, config);
133401
133402         // Create a ddGroup unless one has been configured.
133403         // User configuration of ddGroups allows users to specify which
133404         // DD instances can interact with each other. Using one
133405         // based on the id of the View would isolate it and mean it can only
133406         // interact with a DropZone on the same View also using a generated ID.
133407         if (!me.ddGroup) {
133408             me.ddGroup = 'view-dd-zone-' + me.view.id;
133409         }
133410
133411         // Ext.dd.DragDrop instances are keyed by the ID of their encapsulating element.
133412         // So a View's DragZone cannot use the View's main element because the DropZone must use that
133413         // because the DropZone may need to scroll on hover at a scrolling boundary, and it is the View's
133414         // main element which handles scrolling.
133415         // We use the View's parent element to drag from. Ideally, we would use the internal structure, but that
133416         // is transient; DataView's recreate the internal structure dynamically as data changes.
133417         // TODO: Ext 5.0 DragDrop must allow multiple DD objects to share the same element.
133418         me.callParent([me.view.el.dom.parentNode]);
133419
133420         me.ddel = Ext.get(document.createElement('div'));
133421         me.ddel.addCls(Ext.baseCSSPrefix + 'grid-dd-wrap');
133422     },
133423
133424     init: function(id, sGroup, config) {
133425         this.initTarget(id, sGroup, config);
133426         this.view.mon(this.view, {
133427             itemmousedown: this.onItemMouseDown,
133428             scope: this
133429         });
133430     },
133431
133432     onItemMouseDown: function(view, record, item, index, e) {
133433         if (!this.isPreventDrag(e, record, item, index)) {
133434             this.handleMouseDown(e);
133435
133436             // If we want to allow dragging of multi-selections, then veto the following handlers (which, in the absence of ctrlKey, would deselect)
133437             // if the mousedowned record is selected
133438             if (view.getSelectionModel().selectionMode == 'MULTI' && !e.ctrlKey && view.getSelectionModel().isSelected(record)) {
133439                 return false;
133440             }
133441         }
133442     },
133443
133444     // private template method
133445     isPreventDrag: function(e) {
133446         return false;
133447     },
133448
133449     getDragData: function(e) {
133450         var view = this.view,
133451             item = e.getTarget(view.getItemSelector()),
133452             record, selectionModel, records;
133453
133454         if (item) {
133455             record = view.getRecord(item);
133456             selectionModel = view.getSelectionModel();
133457             records = selectionModel.getSelection();
133458             return {
133459                 copy: this.view.copy || (this.view.allowCopy && e.ctrlKey),
133460                 event: new Ext.EventObjectImpl(e),
133461                 view: view,
133462                 ddel: this.ddel,
133463                 item: item,
133464                 records: records,
133465                 fromPosition: Ext.fly(item).getXY()
133466             };
133467         }
133468     },
133469
133470     onInitDrag: function(x, y) {
133471         var me = this,
133472             data = me.dragData,
133473             view = data.view,
133474             selectionModel = view.getSelectionModel(),
133475             record = view.getRecord(data.item),
133476             e = data.event;
133477
133478         // Update the selection to match what would have been selected if the user had
133479         // done a full click on the target node rather than starting a drag from it
133480         if (!selectionModel.isSelected(record) || e.hasModifier()) {
133481             selectionModel.selectWithEvent(record, e, true);
133482         }
133483         data.records = selectionModel.getSelection();
133484
133485         me.ddel.update(me.getDragText());
133486         me.proxy.update(me.ddel.dom);
133487         me.onStartDrag(x, y);
133488         return true;
133489     },
133490
133491     getDragText: function() {
133492         var count = this.dragData.records.length;
133493         return Ext.String.format(this.dragText, count, count == 1 ? '' : 's');
133494     },
133495
133496     getRepairXY : function(e, data){
133497         return data ? data.fromPosition : false;
133498     }
133499 });
133500 Ext.define('Ext.tree.ViewDragZone', {
133501     extend: 'Ext.view.DragZone',
133502
133503     isPreventDrag: function(e, record) {
133504         return (record.get('allowDrag') === false) || !!e.getTarget(this.view.expanderSelector);
133505     },
133506     
133507     afterRepair: function() {
133508         var me = this,
133509             view = me.view,
133510             selectedRowCls = view.selectedItemCls,
133511             records = me.dragData.records,
133512             fly = Ext.fly;
133513         
133514         if (Ext.enableFx && me.repairHighlight) {
133515             // Roll through all records and highlight all the ones we attempted to drag.
133516             Ext.Array.forEach(records, function(record) {
133517                 // anonymous fns below, don't hoist up unless below is wrapped in
133518                 // a self-executing function passing in item.
133519                 var item = view.getNode(record);
133520                 
133521                 // We must remove the selected row class before animating, because
133522                 // the selected row class declares !important on its background-color.
133523                 fly(item.firstChild).highlight(me.repairHighlightColor, {
133524                     listeners: {
133525                         beforeanimate: function() {
133526                             if (view.isSelected(item)) {
133527                                 fly(item).removeCls(selectedRowCls);
133528                             }
133529                         },
133530                         afteranimate: function() {
133531                             if (view.isSelected(item)) {
133532                                 fly(item).addCls(selectedRowCls);
133533                             }
133534                         }
133535                     }
133536                 });
133537             });
133538         }
133539         me.dragging = false;
133540     }
133541 });
133542 /**
133543  * @class Ext.tree.ViewDropZone
133544  * @extends Ext.view.DropZone
133545  * @private
133546  */
133547 Ext.define('Ext.tree.ViewDropZone', {
133548     extend: 'Ext.view.DropZone',
133549
133550     /**
133551      * @cfg {Boolean} allowParentInsert
133552      * Allow inserting a dragged node between an expanded parent node and its first child that will become a
133553      * sibling of the parent when dropped.
133554      */
133555     allowParentInserts: false,
133556  
133557     /**
133558      * @cfg {String} allowContainerDrop
133559      * True if drops on the tree container (outside of a specific tree node) are allowed.
133560      */
133561     allowContainerDrops: false,
133562
133563     /**
133564      * @cfg {String} appendOnly
133565      * True if the tree should only allow append drops (use for trees which are sorted).
133566      */
133567     appendOnly: false,
133568
133569     /**
133570      * @cfg {String} expandDelay
133571      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
133572      * over the target.
133573      */
133574     expandDelay : 500,
133575
133576     indicatorCls: 'x-tree-ddindicator',
133577
133578     // private
133579     expandNode : function(node) {
133580         var view = this.view;
133581         if (!node.isLeaf() && !node.isExpanded()) {
133582             view.expand(node);
133583             this.expandProcId = false;
133584         }
133585     },
133586
133587     // private
133588     queueExpand : function(node) {
133589         this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]);
133590     },
133591
133592     // private
133593     cancelExpand : function() {
133594         if (this.expandProcId) {
133595             clearTimeout(this.expandProcId);
133596             this.expandProcId = false;
133597         }
133598     },
133599
133600     getPosition: function(e, node) {
133601         var view = this.view,
133602             record = view.getRecord(node),
133603             y = e.getPageY(),
133604             noAppend = record.isLeaf(),
133605             noBelow = false,
133606             region = Ext.fly(node).getRegion(),
133607             fragment;
133608
133609         // If we are dragging on top of the root node of the tree, we always want to append.
133610         if (record.isRoot()) {
133611             return 'append';
133612         }
133613
133614         // Return 'append' if the node we are dragging on top of is not a leaf else return false.
133615         if (this.appendOnly) {
133616             return noAppend ? false : 'append';
133617         }
133618
133619         if (!this.allowParentInsert) {
133620             noBelow = record.hasChildNodes() && record.isExpanded();
133621         }
133622
133623         fragment = (region.bottom - region.top) / (noAppend ? 2 : 3);
133624         if (y >= region.top && y < (region.top + fragment)) {
133625             return 'before';
133626         }
133627         else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) {
133628             return 'after';
133629         }
133630         else {
133631             return 'append';
133632         }
133633     },
133634
133635     isValidDropPoint : function(node, position, dragZone, e, data) {
133636         if (!node || !data.item) {
133637             return false;
133638         }
133639
133640         var view = this.view,
133641             targetNode = view.getRecord(node),
133642             draggedRecords = data.records,
133643             dataLength = draggedRecords.length,
133644             ln = draggedRecords.length,
133645             i, record;
133646
133647         // No drop position, or dragged records: invalid drop point
133648         if (!(targetNode && position && dataLength)) {
133649             return false;
133650         }
133651
133652         // If the targetNode is within the folder we are dragging
133653         for (i = 0; i < ln; i++) {
133654             record = draggedRecords[i];
133655             if (record.isNode && record.contains(targetNode)) {
133656                 return false;
133657             }
133658         }
133659         
133660         // Respect the allowDrop field on Tree nodes
133661         if (position === 'append' && targetNode.get('allowDrop') === false) {
133662             return false;
133663         }
133664         else if (position != 'append' && targetNode.parentNode.get('allowDrop') === false) {
133665             return false;
133666         }
133667
133668         // If the target record is in the dragged dataset, then invalid drop
133669         if (Ext.Array.contains(draggedRecords, targetNode)) {
133670              return false;
133671         }
133672
133673         // @TODO: fire some event to notify that there is a valid drop possible for the node you're dragging
133674         // Yes: this.fireViewEvent(blah....) fires an event through the owning View.
133675         return true;
133676     },
133677
133678     onNodeOver : function(node, dragZone, e, data) {
133679         var position = this.getPosition(e, node),
133680             returnCls = this.dropNotAllowed,
133681             view = this.view,
133682             targetNode = view.getRecord(node),
133683             indicator = this.getIndicator(),
133684             indicatorX = 0,
133685             indicatorY = 0;
133686
133687         // auto node expand check
133688         this.cancelExpand();
133689         if (position == 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) {
133690             this.queueExpand(targetNode);
133691         }
133692             
133693             
133694         if (this.isValidDropPoint(node, position, dragZone, e, data)) {
133695             this.valid = true;
133696             this.currentPosition = position;
133697             this.overRecord = targetNode;
133698
133699             indicator.setWidth(Ext.fly(node).getWidth());
133700             indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1;
133701
133702             /*
133703              * In the code below we show the proxy again. The reason for doing this is showing the indicator will
133704              * call toFront, causing it to get a new z-index which can sometimes push the proxy behind it. We always 
133705              * want the proxy to be above, so calling show on the proxy will call toFront and bring it forward.
133706              */
133707             if (position == 'before') {
133708                 returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
133709                 indicator.showAt(0, indicatorY);
133710                 dragZone.proxy.show();
133711             } else if (position == 'after') {
133712                 returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
133713                 indicatorY += Ext.fly(node).getHeight();
133714                 indicator.showAt(0, indicatorY);
133715                 dragZone.proxy.show();
133716             } else {
133717                 returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append';
133718                 // @TODO: set a class on the parent folder node to be able to style it
133719                 indicator.hide();
133720             }
133721         } else {
133722             this.valid = false;
133723         }
133724
133725         this.currentCls = returnCls;
133726         return returnCls;
133727     },
133728
133729     onContainerOver : function(dd, e, data) {
133730         return e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed;
133731     },
133732     
133733     notifyOut: function() {
133734         this.callParent(arguments);
133735         this.cancelExpand();
133736     },
133737
133738     handleNodeDrop : function(data, targetNode, position) {
133739         var me = this,
133740             view = me.view,
133741             parentNode = targetNode.parentNode,
133742             store = view.getStore(),
133743             recordDomNodes = [],
133744             records, i, len,
133745             insertionMethod, argList,
133746             needTargetExpand,
133747             transferData,
133748             processDrop;
133749
133750         // If the copy flag is set, create a copy of the Models with the same IDs
133751         if (data.copy) {
133752             records = data.records;
133753             data.records = [];
133754             for (i = 0, len = records.length; i < len; i++) {
133755                 data.records.push(Ext.apply({}, records[i].data));
133756             }
133757         }
133758
133759         // Cancel any pending expand operation
133760         me.cancelExpand();
133761
133762         // Grab a reference to the correct node insertion method.
133763         // Create an arg list array intended for the apply method of the
133764         // chosen node insertion method.
133765         // Ensure the target object for the method is referenced by 'targetNode'
133766         if (position == 'before') {
133767             insertionMethod = parentNode.insertBefore;
133768             argList = [null, targetNode];
133769             targetNode = parentNode;
133770         }
133771         else if (position == 'after') {
133772             if (targetNode.nextSibling) {
133773                 insertionMethod = parentNode.insertBefore;
133774                 argList = [null, targetNode.nextSibling];
133775             }
133776             else {
133777                 insertionMethod = parentNode.appendChild;
133778                 argList = [null];
133779             }
133780             targetNode = parentNode;
133781         }
133782         else {
133783             if (!targetNode.isExpanded()) {
133784                 needTargetExpand = true;
133785             }
133786             insertionMethod = targetNode.appendChild;
133787             argList = [null];
133788         }
133789
133790         // A function to transfer the data into the destination tree
133791         transferData = function() {
133792             var node;
133793             for (i = 0, len = data.records.length; i < len; i++) {
133794                 argList[0] = data.records[i];
133795                 node = insertionMethod.apply(targetNode, argList);
133796                 
133797                 if (Ext.enableFx && me.dropHighlight) {
133798                     recordDomNodes.push(view.getNode(node));
133799                 }
133800             }
133801             
133802             // Kick off highlights after everything's been inserted, so they are
133803             // more in sync without insertion/render overhead.
133804             if (Ext.enableFx && me.dropHighlight) {
133805                 //FIXME: the check for n.firstChild is not a great solution here. Ideally the line should simply read 
133806                 //Ext.fly(n.firstChild) but this yields errors in IE6 and 7. See ticket EXTJSIV-1705 for more details
133807                 Ext.Array.forEach(recordDomNodes, function(n) {
133808                     if (n) {
133809                         Ext.fly(n.firstChild ? n.firstChild : n).highlight(me.dropHighlightColor);
133810                     }
133811                 });
133812             }
133813         };
133814
133815         // If dropping right on an unexpanded node, transfer the data after it is expanded.
133816         if (needTargetExpand) {
133817             targetNode.expand(false, transferData);
133818         }
133819         // Otherwise, call the data transfer function immediately
133820         else {
133821             transferData();
133822         }
133823     }
133824 });
133825 /**
133826  * This plugin provides drag and/or drop functionality for a TreeView.
133827  *
133828  * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a
133829  * {@link Ext.tree.View TreeView} and loads the data object which is passed to a cooperating
133830  * {@link Ext.dd.DragZone DragZone}'s methods with the following properties:
133831  *
133832  *   - copy : Boolean
133833  *
133834  *     The value of the TreeView's `copy` property, or `true` if the TreeView was configured with `allowCopy: true` *and*
133835  *     the control key was pressed when the drag operation was begun.
133836  *
133837  *   - view : TreeView
133838  *
133839  *     The source TreeView from which the drag originated.
133840  *
133841  *   - ddel : HtmlElement
133842  *
133843  *     The drag proxy element which moves with the mouse
133844  *
133845  *   - item : HtmlElement
133846  *
133847  *     The TreeView node upon which the mousedown event was registered.
133848  *
133849  *   - records : Array
133850  *
133851  *     An Array of {@link Ext.data.Model Models} representing the selected data being dragged from the source TreeView.
133852  *
133853  * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
133854  * members of the same ddGroup which processes such data objects.
133855  *
133856  * Adding this plugin to a view means that two new events may be fired from the client TreeView, {@link #beforedrop} and
133857  * {@link #drop}.
133858  *
133859  * Note that the plugin must be added to the tree view, not to the tree panel. For example using viewConfig:
133860  *
133861  *     viewConfig: {
133862  *         plugins: { ptype: 'treeviewdragdrop' }
133863  *     }
133864  */
133865 Ext.define('Ext.tree.plugin.TreeViewDragDrop', {
133866     extend: 'Ext.AbstractPlugin',
133867     alias: 'plugin.treeviewdragdrop',
133868
133869     uses: [
133870         'Ext.tree.ViewDragZone',
133871         'Ext.tree.ViewDropZone'
133872     ],
133873
133874     /**
133875      * @event beforedrop
133876      *
133877      * **This event is fired through the TreeView. Add listeners to the TreeView object**
133878      *
133879      * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the TreeView.
133880      *
133881      * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
133882      *
133883      * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
133884      * back to the point from which the drag began.
133885      *
133886      * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
133887      * was valid, and that the repair operation should not take place.
133888      *
133889      * Any other return value continues with the data transfer operation.
133890      *
133891      * @param {Object} data The data object gathered at mousedown time by the cooperating
133892      * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
133893      * properties:
133894      * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
133895      * `allowCopy: true` and the control key was pressed when the drag operation was begun
133896      * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
133897      * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
133898      * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
133899      * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
133900      * dragged from the source TreeView.
133901      *
133902      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
133903      *
133904      * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
133905      * the midline of the node, or the node is a branch node which accepts new child nodes.
133906      *
133907      * @param {Function} dropFunction A function to call to complete the data transfer operation and either move or copy
133908      * Model instances from the source View's Store to the destination View's Store.
133909      *
133910      * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
133911      * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
133912      *
133913      * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
133914      */
133915
133916     /**
133917      * @event drop
133918      *
133919      * **This event is fired through the TreeView. Add listeners to the TreeView object** Fired when a drop operation
133920      * has been completed and the data has been moved or copied.
133921      *
133922      * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
133923      *
133924      * @param {Object} data The data object gathered at mousedown time by the cooperating
133925      * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
133926      * properties:
133927      * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
133928      * `allowCopy: true` and the control key was pressed when the drag operation was begun
133929      * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
133930      * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
133931      * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
133932      * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
133933      * dragged from the source TreeView.
133934      *
133935      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
133936      *
133937      * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
133938      * the midline of the node, or the node is a branch node which accepts new child nodes.
133939      */
133940
133941     dragText : '{0} selected node{1}',
133942
133943     /**
133944      * @cfg {Boolean} allowParentInsert
133945      * Allow inserting a dragged node between an expanded parent node and its first child that will become a sibling of
133946      * the parent when dropped.
133947      */
133948     allowParentInserts: false,
133949
133950     /**
133951      * @cfg {String} allowContainerDrop
133952      * True if drops on the tree container (outside of a specific tree node) are allowed.
133953      */
133954     allowContainerDrops: false,
133955
133956     /**
133957      * @cfg {String} appendOnly
133958      * True if the tree should only allow append drops (use for trees which are sorted).
133959      */
133960     appendOnly: false,
133961
133962     /**
133963      * @cfg {String} ddGroup
133964      * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
133965      * DropZone used by this plugin will only interact with other drag drop objects in the same group.
133966      */
133967     ddGroup : "TreeDD",
133968
133969     /**
133970      * @cfg {String} dragGroup
133971      * The ddGroup to which the DragZone will belong.
133972      *
133973      * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
133974      * Drag/DropZones which are members of the same ddGroup.
133975      */
133976
133977     /**
133978      * @cfg {String} dropGroup
133979      * The ddGroup to which the DropZone will belong.
133980      *
133981      * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
133982      * Drag/DropZones which are members of the same ddGroup.
133983      */
133984
133985     /**
133986      * @cfg {String} expandDelay
133987      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node over the
133988      * target.
133989      */
133990     expandDelay : 1000,
133991
133992     /**
133993      * @cfg {Boolean} enableDrop
133994      * Set to `false` to disallow the View from accepting drop gestures.
133995      */
133996     enableDrop: true,
133997
133998     /**
133999      * @cfg {Boolean} enableDrag
134000      * Set to `false` to disallow dragging items from the View.
134001      */
134002     enableDrag: true,
134003
134004     /**
134005      * @cfg {String} nodeHighlightColor
134006      * The color to use when visually highlighting the dragged or dropped node (default value is light blue).
134007      * The color must be a 6 digit hex value, without a preceding '#'. See also {@link #nodeHighlightOnDrop} and
134008      * {@link #nodeHighlightOnRepair}.
134009      */
134010     nodeHighlightColor: 'c3daf9',
134011
134012     /**
134013      * @cfg {Boolean} nodeHighlightOnDrop
134014      * Whether or not to highlight any nodes after they are
134015      * successfully dropped on their target. Defaults to the value of `Ext.enableFx`.
134016      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnRepair}.
134017      */
134018     nodeHighlightOnDrop: Ext.enableFx,
134019
134020     /**
134021      * @cfg {Boolean} nodeHighlightOnRepair
134022      * Whether or not to highlight any nodes after they are
134023      * repaired from an unsuccessful drag/drop. Defaults to the value of `Ext.enableFx`.
134024      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnDrop}.
134025      */
134026     nodeHighlightOnRepair: Ext.enableFx,
134027
134028     init : function(view) {
134029         view.on('render', this.onViewRender, this, {single: true});
134030     },
134031
134032     /**
134033      * @private
134034      * AbstractComponent calls destroy on all its plugins at destroy time.
134035      */
134036     destroy: function() {
134037         Ext.destroy(this.dragZone, this.dropZone);
134038     },
134039
134040     onViewRender : function(view) {
134041         var me = this;
134042
134043         if (me.enableDrag) {
134044             me.dragZone = Ext.create('Ext.tree.ViewDragZone', {
134045                 view: view,
134046                 ddGroup: me.dragGroup || me.ddGroup,
134047                 dragText: me.dragText,
134048                 repairHighlightColor: me.nodeHighlightColor,
134049                 repairHighlight: me.nodeHighlightOnRepair
134050             });
134051         }
134052
134053         if (me.enableDrop) {
134054             me.dropZone = Ext.create('Ext.tree.ViewDropZone', {
134055                 view: view,
134056                 ddGroup: me.dropGroup || me.ddGroup,
134057                 allowContainerDrops: me.allowContainerDrops,
134058                 appendOnly: me.appendOnly,
134059                 allowParentInserts: me.allowParentInserts,
134060                 expandDelay: me.expandDelay,
134061                 dropHighlightColor: me.nodeHighlightColor,
134062                 dropHighlight: me.nodeHighlightOnDrop
134063             });
134064         }
134065     }
134066 });
134067 /**
134068  * @class Ext.util.Cookies
134069
134070 Utility class for setting/reading values from browser cookies.
134071 Values can be written using the {@link #set} method.
134072 Values can be read using the {@link #get} method.
134073 A cookie can be invalidated on the client machine using the {@link #clear} method.
134074
134075  * @markdown
134076  * @singleton
134077  */
134078 Ext.define('Ext.util.Cookies', {
134079     singleton: true,
134080     
134081     /**
134082      * Create a cookie with the specified name and value. Additional settings
134083      * for the cookie may be optionally specified (for example: expiration,
134084      * access restriction, SSL).
134085      * @param {String} name The name of the cookie to set. 
134086      * @param {Object} value The value to set for the cookie.
134087      * @param {Object} expires (Optional) Specify an expiration date the
134088      * cookie is to persist until.  Note that the specified Date object will
134089      * be converted to Greenwich Mean Time (GMT). 
134090      * @param {String} path (Optional) Setting a path on the cookie restricts
134091      * access to pages that match that path. Defaults to all pages (<tt>'/'</tt>). 
134092      * @param {String} domain (Optional) Setting a domain restricts access to
134093      * pages on a given domain (typically used to allow cookie access across
134094      * subdomains). For example, "sencha.com" will create a cookie that can be
134095      * accessed from any subdomain of sencha.com, including www.sencha.com,
134096      * support.sencha.com, etc.
134097      * @param {Boolean} secure (Optional) Specify true to indicate that the cookie
134098      * should only be accessible via SSL on a page using the HTTPS protocol.
134099      * Defaults to <tt>false</tt>. Note that this will only work if the page
134100      * calling this code uses the HTTPS protocol, otherwise the cookie will be
134101      * created with default options.
134102      */
134103     set : function(name, value){
134104         var argv = arguments,
134105             argc = arguments.length,
134106             expires = (argc > 2) ? argv[2] : null,
134107             path = (argc > 3) ? argv[3] : '/',
134108             domain = (argc > 4) ? argv[4] : null,
134109             secure = (argc > 5) ? argv[5] : false;
134110             
134111         document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
134112     },
134113
134114     /**
134115      * Retrieves cookies that are accessible by the current page. If a cookie
134116      * does not exist, <code>get()</code> returns <tt>null</tt>.  The following
134117      * example retrieves the cookie called "valid" and stores the String value
134118      * in the variable <tt>validStatus</tt>.
134119      * <pre><code>
134120      * var validStatus = Ext.util.Cookies.get("valid");
134121      * </code></pre>
134122      * @param {String} name The name of the cookie to get
134123      * @return {Object} Returns the cookie value for the specified name;
134124      * null if the cookie name does not exist.
134125      */
134126     get : function(name){
134127         var arg = name + "=",
134128             alen = arg.length,
134129             clen = document.cookie.length,
134130             i = 0,
134131             j = 0;
134132             
134133         while(i < clen){
134134             j = i + alen;
134135             if(document.cookie.substring(i, j) == arg){
134136                 return this.getCookieVal(j);
134137             }
134138             i = document.cookie.indexOf(" ", i) + 1;
134139             if(i === 0){
134140                 break;
134141             }
134142         }
134143         return null;
134144     },
134145
134146     /**
134147      * Removes a cookie with the provided name from the browser
134148      * if found by setting its expiration date to sometime in the past. 
134149      * @param {String} name The name of the cookie to remove
134150      * @param {String} path (optional) The path for the cookie. This must be included if you included a path while setting the cookie.
134151      */
134152     clear : function(name, path){
134153         if(this.get(name)){
134154             path = path || '/';
134155             document.cookie = name + '=' + '; expires=Thu, 01-Jan-70 00:00:01 GMT; path=' + path;
134156         }
134157     },
134158     
134159     /**
134160      * @private
134161      */
134162     getCookieVal : function(offset){
134163         var endstr = document.cookie.indexOf(";", offset);
134164         if(endstr == -1){
134165             endstr = document.cookie.length;
134166         }
134167         return unescape(document.cookie.substring(offset, endstr));
134168     }
134169 });
134170
134171 /**
134172  * @class Ext.util.CSS
134173  * Utility class for manipulating CSS rules
134174  * @singleton
134175  */
134176 Ext.define('Ext.util.CSS', function() {
134177     var rules = null;
134178     var doc = document;
134179
134180     var camelRe = /(-[a-z])/gi;
134181     var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
134182
134183     return {
134184
134185         singleton: true,
134186
134187         constructor: function() {
134188             this.rules = {};
134189             this.initialized = false;
134190         },
134191
134192         /**
134193          * Creates a stylesheet from a text blob of rules.
134194          * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
134195          * @param {String} cssText The text containing the css rules
134196          * @param {String} id An id to add to the stylesheet for later removal
134197          * @return {CSSStyleSheet}
134198          */
134199         createStyleSheet : function(cssText, id) {
134200             var ss,
134201                 head = doc.getElementsByTagName("head")[0],
134202                 styleEl = doc.createElement("style");
134203
134204             styleEl.setAttribute("type", "text/css");
134205             if (id) {
134206                styleEl.setAttribute("id", id);
134207             }
134208
134209             if (Ext.isIE) {
134210                head.appendChild(styleEl);
134211                ss = styleEl.styleSheet;
134212                ss.cssText = cssText;
134213             } else {
134214                 try{
134215                     styleEl.appendChild(doc.createTextNode(cssText));
134216                 } catch(e) {
134217                    styleEl.cssText = cssText;
134218                 }
134219                 head.appendChild(styleEl);
134220                 ss = styleEl.styleSheet ? styleEl.styleSheet : (styleEl.sheet || doc.styleSheets[doc.styleSheets.length-1]);
134221             }
134222             this.cacheStyleSheet(ss);
134223             return ss;
134224         },
134225
134226         /**
134227          * Removes a style or link tag by id
134228          * @param {String} id The id of the tag
134229          */
134230         removeStyleSheet : function(id) {
134231             var existing = document.getElementById(id);
134232             if (existing) {
134233                 existing.parentNode.removeChild(existing);
134234             }
134235         },
134236
134237         /**
134238          * Dynamically swaps an existing stylesheet reference for a new one
134239          * @param {String} id The id of an existing link tag to remove
134240          * @param {String} url The href of the new stylesheet to include
134241          */
134242         swapStyleSheet : function(id, url) {
134243             var doc = document;
134244             this.removeStyleSheet(id);
134245             var ss = doc.createElement("link");
134246             ss.setAttribute("rel", "stylesheet");
134247             ss.setAttribute("type", "text/css");
134248             ss.setAttribute("id", id);
134249             ss.setAttribute("href", url);
134250             doc.getElementsByTagName("head")[0].appendChild(ss);
134251         },
134252
134253         /**
134254          * Refresh the rule cache if you have dynamically added stylesheets
134255          * @return {Object} An object (hash) of rules indexed by selector
134256          */
134257         refreshCache : function() {
134258             return this.getRules(true);
134259         },
134260
134261         // private
134262         cacheStyleSheet : function(ss) {
134263             if(!rules){
134264                 rules = {};
134265             }
134266             try {// try catch for cross domain access issue
134267                 var ssRules = ss.cssRules || ss.rules,
134268                     selectorText,
134269                     i = ssRules.length - 1,
134270                     j,
134271                     selectors;
134272
134273                 for (; i >= 0; --i) {
134274                     selectorText = ssRules[i].selectorText;
134275                     if (selectorText) {
134276
134277                         // Split in case there are multiple, comma-delimited selectors
134278                         selectorText = selectorText.split(',');
134279                         selectors = selectorText.length;
134280                         for (j = 0; j < selectors; j++) {
134281                             rules[Ext.String.trim(selectorText[j]).toLowerCase()] = ssRules[i];
134282                         }
134283                     }
134284                 }
134285             } catch(e) {}
134286         },
134287
134288         /**
134289         * Gets all css rules for the document
134290         * @param {Boolean} refreshCache true to refresh the internal cache
134291         * @return {Object} An object (hash) of rules indexed by selector
134292         */
134293         getRules : function(refreshCache) {
134294             if (rules === null || refreshCache) {
134295                 rules = {};
134296                 var ds = doc.styleSheets,
134297                     i = 0,
134298                     len = ds.length;
134299
134300                 for (; i < len; i++) {
134301                     try {
134302                         if (!ds[i].disabled) {
134303                             this.cacheStyleSheet(ds[i]);
134304                         }
134305                     } catch(e) {}
134306                 }
134307             }
134308             return rules;
134309         },
134310
134311         /**
134312          * Gets an an individual CSS rule by selector(s)
134313          * @param {String/String[]} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
134314          * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
134315          * @return {CSSStyleRule} The CSS rule or null if one is not found
134316          */
134317         getRule: function(selector, refreshCache) {
134318             var rs = this.getRules(refreshCache);
134319             if (!Ext.isArray(selector)) {
134320                 return rs[selector.toLowerCase()];
134321             }
134322             for (var i = 0; i < selector.length; i++) {
134323                 if (rs[selector[i]]) {
134324                     return rs[selector[i].toLowerCase()];
134325                 }
134326             }
134327             return null;
134328         },
134329
134330         /**
134331          * Updates a rule property
134332          * @param {String/String[]} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
134333          * @param {String} property The css property
134334          * @param {String} value The new value for the property
134335          * @return {Boolean} true If a rule was found and updated
134336          */
134337         updateRule : function(selector, property, value){
134338             if (!Ext.isArray(selector)) {
134339                 var rule = this.getRule(selector);
134340                 if (rule) {
134341                     rule.style[property.replace(camelRe, camelFn)] = value;
134342                     return true;
134343                 }
134344             } else {
134345                 for (var i = 0; i < selector.length; i++) {
134346                     if (this.updateRule(selector[i], property, value)) {
134347                         return true;
134348                     }
134349                 }
134350             }
134351             return false;
134352         }
134353     };
134354 }());
134355 /**
134356  * @class Ext.util.History
134357  *
134358  * History management component that allows you to register arbitrary tokens that signify application
134359  * history state on navigation actions.  You can then handle the history {@link #change} event in order
134360  * to reset your application UI to the appropriate state when the user navigates forward or backward through
134361  * the browser history stack.
134362  *
134363  * ## Initializing
134364  * The {@link #init} method of the History object must be called before using History. This sets up the internal
134365  * state and must be the first thing called before using History.
134366  *
134367  * ## Setup
134368  * The History objects requires elements on the page to keep track of the browser history. For older versions of IE,
134369  * an IFrame is required to do the tracking. For other browsers, a hidden field can be used. The history objects expects
134370  * these to be on the page before the {@link #init} method is called. The following markup is suggested in order
134371  * to support all browsers:
134372  *
134373  *     <form id="history-form" class="x-hide-display">
134374  *         <input type="hidden" id="x-history-field" />
134375  *         <iframe id="x-history-frame"></iframe>
134376  *     </form>
134377  *
134378  * @singleton
134379  */
134380 Ext.define('Ext.util.History', {
134381     singleton: true,
134382     alternateClassName: 'Ext.History',
134383     mixins: {
134384         observable: 'Ext.util.Observable'
134385     },
134386
134387     constructor: function() {
134388         var me = this;
134389         me.oldIEMode = Ext.isIE6 || Ext.isIE7 || !Ext.isStrict && Ext.isIE8;
134390         me.iframe = null;
134391         me.hiddenField = null;
134392         me.ready = false;
134393         me.currentToken = null;
134394     },
134395
134396     getHash: function() {
134397         var href = window.location.href,
134398             i = href.indexOf("#");
134399
134400         return i >= 0 ? href.substr(i + 1) : null;
134401     },
134402
134403     doSave: function() {
134404         this.hiddenField.value = this.currentToken;
134405     },
134406
134407
134408     handleStateChange: function(token) {
134409         this.currentToken = token;
134410         this.fireEvent('change', token);
134411     },
134412
134413     updateIFrame: function(token) {
134414         var html = '<html><body><div id="state">' +
134415                     Ext.util.Format.htmlEncode(token) +
134416                     '</div></body></html>';
134417
134418         try {
134419             var doc = this.iframe.contentWindow.document;
134420             doc.open();
134421             doc.write(html);
134422             doc.close();
134423             return true;
134424         } catch (e) {
134425             return false;
134426         }
134427     },
134428
134429     checkIFrame: function () {
134430         var me = this,
134431             contentWindow = me.iframe.contentWindow;
134432
134433         if (!contentWindow || !contentWindow.document) {
134434             Ext.Function.defer(this.checkIFrame, 10, this);
134435             return;
134436         }
134437
134438         var doc = contentWindow.document,
134439             elem = doc.getElementById("state"),
134440             oldToken = elem ? elem.innerText : null,
134441             oldHash = me.getHash();
134442
134443         Ext.TaskManager.start({
134444             run: function () {
134445                 var doc = contentWindow.document,
134446                     elem = doc.getElementById("state"),
134447                     newToken = elem ? elem.innerText : null,
134448                     newHash = me.getHash();
134449
134450                 if (newToken !== oldToken) {
134451                     oldToken = newToken;
134452                     me.handleStateChange(newToken);
134453                     window.top.location.hash = newToken;
134454                     oldHash = newToken;
134455                     me.doSave();
134456                 } else if (newHash !== oldHash) {
134457                     oldHash = newHash;
134458                     me.updateIFrame(newHash);
134459                 }
134460             },
134461             interval: 50,
134462             scope: me
134463         });
134464         me.ready = true;
134465         me.fireEvent('ready', me);
134466     },
134467
134468     startUp: function () {
134469         var me = this;
134470
134471         me.currentToken = me.hiddenField.value || this.getHash();
134472
134473         if (me.oldIEMode) {
134474             me.checkIFrame();
134475         } else {
134476             var hash = me.getHash();
134477             Ext.TaskManager.start({
134478                 run: function () {
134479                     var newHash = me.getHash();
134480                     if (newHash !== hash) {
134481                         hash = newHash;
134482                         me.handleStateChange(hash);
134483                         me.doSave();
134484                     }
134485                 },
134486                 interval: 50,
134487                 scope: me
134488             });
134489             me.ready = true;
134490             me.fireEvent('ready', me);
134491         }
134492
134493     },
134494
134495     /**
134496      * The id of the hidden field required for storing the current history token.
134497      * @type String
134498      * @property
134499      */
134500     fieldId: Ext.baseCSSPrefix + 'history-field',
134501     /**
134502      * The id of the iframe required by IE to manage the history stack.
134503      * @type String
134504      * @property
134505      */
134506     iframeId: Ext.baseCSSPrefix + 'history-frame',
134507
134508     /**
134509      * Initialize the global History instance.
134510      * @param {Boolean} onReady (optional) A callback function that will be called once the history
134511      * component is fully initialized.
134512      * @param {Object} scope (optional) The scope (`this` reference) in which the callback is executed. Defaults to the browser window.
134513      */
134514     init: function (onReady, scope) {
134515         var me = this;
134516
134517         if (me.ready) {
134518             Ext.callback(onReady, scope, [me]);
134519             return;
134520         }
134521
134522         if (!Ext.isReady) {
134523             Ext.onReady(function() {
134524                 me.init(onReady, scope);
134525             });
134526             return;
134527         }
134528
134529         me.hiddenField = Ext.getDom(me.fieldId);
134530
134531         if (me.oldIEMode) {
134532             me.iframe = Ext.getDom(me.iframeId);
134533         }
134534
134535         me.addEvents(
134536             /**
134537              * @event ready
134538              * Fires when the Ext.util.History singleton has been initialized and is ready for use.
134539              * @param {Ext.util.History} The Ext.util.History singleton.
134540              */
134541             'ready',
134542             /**
134543              * @event change
134544              * Fires when navigation back or forwards within the local page's history occurs.
134545              * @param {String} token An identifier associated with the page state at that point in its history.
134546              */
134547             'change'
134548         );
134549
134550         if (onReady) {
134551             me.on('ready', onReady, scope, {single: true});
134552         }
134553         me.startUp();
134554     },
134555
134556     /**
134557      * Add a new token to the history stack. This can be any arbitrary value, although it would
134558      * commonly be the concatenation of a component id and another id marking the specific history
134559      * state of that component. Example usage:
134560      *
134561      *     // Handle tab changes on a TabPanel
134562      *     tabPanel.on('tabchange', function(tabPanel, tab){
134563      *          Ext.History.add(tabPanel.id + ':' + tab.id);
134564      *     });
134565      *
134566      * @param {String} token The value that defines a particular application-specific history state
134567      * @param {Boolean} [preventDuplicates=true] When true, if the passed token matches the current token
134568      * it will not save a new history step. Set to false if the same state can be saved more than once
134569      * at the same history stack location.
134570      */
134571     add: function (token, preventDup) {
134572         var me = this;
134573
134574         if (preventDup !== false) {
134575             if (me.getToken() === token) {
134576                 return true;
134577             }
134578         }
134579
134580         if (me.oldIEMode) {
134581             return me.updateIFrame(token);
134582         } else {
134583             window.top.location.hash = token;
134584             return true;
134585         }
134586     },
134587
134588     /**
134589      * Programmatically steps back one step in browser history (equivalent to the user pressing the Back button).
134590      */
134591     back: function() {
134592         window.history.go(-1);
134593     },
134594
134595     /**
134596      * Programmatically steps forward one step in browser history (equivalent to the user pressing the Forward button).
134597      */
134598     forward: function(){
134599         window.history.go(1);
134600     },
134601
134602     /**
134603      * Retrieves the currently-active history token.
134604      * @return {String} The token
134605      */
134606     getToken: function() {
134607         return this.ready ? this.currentToken : this.getHash();
134608     }
134609 });
134610 /**
134611  * @class Ext.view.TableChunker
134612  * 
134613  * Produces optimized XTemplates for chunks of tables to be
134614  * used in grids, trees and other table based widgets.
134615  *
134616  * @singleton
134617  */
134618 Ext.define('Ext.view.TableChunker', {
134619     singleton: true,
134620     requires: ['Ext.XTemplate'],
134621     metaTableTpl: [
134622         '{[this.openTableWrap()]}',
134623         '<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" border="0" cellspacing="0" cellpadding="0" {[this.embedFullWidth()]}>',
134624             '<tbody>',
134625             '<tr class="' + Ext.baseCSSPrefix + 'grid-header-row">',
134626             '<tpl for="columns">',
134627                 '<th class="' + Ext.baseCSSPrefix + 'grid-col-resizer-{id}" style="width: {width}px; height: 0px;"></th>',
134628             '</tpl>',
134629             '</tr>',
134630             '{[this.openRows()]}',
134631                 '{row}',
134632                 '<tpl for="features">',
134633                     '{[this.embedFeature(values, parent, xindex, xcount)]}',
134634                 '</tpl>',
134635             '{[this.closeRows()]}',
134636             '</tbody>',
134637         '</table>',
134638         '{[this.closeTableWrap()]}'
134639     ],
134640
134641     constructor: function() {
134642         Ext.XTemplate.prototype.recurse = function(values, reference) {
134643             return this.apply(reference ? values[reference] : values);
134644         };
134645     },
134646
134647     embedFeature: function(values, parent, x, xcount) {
134648         var tpl = '';
134649         if (!values.disabled) {
134650             tpl = values.getFeatureTpl(values, parent, x, xcount);
134651         }
134652         return tpl;
134653     },
134654
134655     embedFullWidth: function() {
134656         return 'style="width: {fullWidth}px;"';
134657     },
134658
134659     openRows: function() {
134660         return '<tpl for="rows">';
134661     },
134662
134663     closeRows: function() {
134664         return '</tpl>';
134665     },
134666
134667     metaRowTpl: [
134668         '<tr class="' + Ext.baseCSSPrefix + 'grid-row {addlSelector} {[this.embedRowCls()]}" {[this.embedRowAttr()]}>',
134669             '<tpl for="columns">',
134670                 '<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>',
134671             '</tpl>',
134672         '</tr>'
134673     ],
134674     
134675     firstOrLastCls: function(xindex, xcount) {
134676         var cssCls = '';
134677         if (xindex === 1) {
134678             cssCls = Ext.baseCSSPrefix + 'grid-cell-first';
134679         } else if (xindex === xcount) {
134680             cssCls = Ext.baseCSSPrefix + 'grid-cell-last';
134681         }
134682         return cssCls;
134683     },
134684     
134685     embedRowCls: function() {
134686         return '{rowCls}';
134687     },
134688     
134689     embedRowAttr: function() {
134690         return '{rowAttr}';
134691     },
134692     
134693     openTableWrap: function() {
134694         return '';
134695     },
134696     
134697     closeTableWrap: function() {
134698         return '';
134699     },
134700
134701     getTableTpl: function(cfg, textOnly) {
134702         var tpl,
134703             tableTplMemberFns = {
134704                 openRows: this.openRows,
134705                 closeRows: this.closeRows,
134706                 embedFeature: this.embedFeature,
134707                 embedFullWidth: this.embedFullWidth,
134708                 openTableWrap: this.openTableWrap,
134709                 closeTableWrap: this.closeTableWrap
134710             },
134711             tplMemberFns = {},
134712             features = cfg.features || [],
134713             ln = features.length,
134714             i  = 0,
134715             memberFns = {
134716                 embedRowCls: this.embedRowCls,
134717                 embedRowAttr: this.embedRowAttr,
134718                 firstOrLastCls: this.firstOrLastCls
134719             },
134720             // copy the default
134721             metaRowTpl = Array.prototype.slice.call(this.metaRowTpl, 0),
134722             metaTableTpl;
134723             
134724         for (; i < ln; i++) {
134725             if (!features[i].disabled) {
134726                 features[i].mutateMetaRowTpl(metaRowTpl);
134727                 Ext.apply(memberFns, features[i].getMetaRowTplFragments());
134728                 Ext.apply(tplMemberFns, features[i].getFragmentTpl());
134729                 Ext.apply(tableTplMemberFns, features[i].getTableFragments());
134730             }
134731         }
134732         
134733         metaRowTpl = Ext.create('Ext.XTemplate', metaRowTpl.join(''), memberFns);
134734         cfg.row = metaRowTpl.applyTemplate(cfg);
134735         
134736         metaTableTpl = Ext.create('Ext.XTemplate', this.metaTableTpl.join(''), tableTplMemberFns);
134737         
134738         tpl = metaTableTpl.applyTemplate(cfg);
134739         
134740         // TODO: Investigate eliminating.
134741         if (!textOnly) {
134742             tpl = Ext.create('Ext.XTemplate', tpl, tplMemberFns);
134743         }
134744         return tpl;
134745         
134746     }
134747 });
134748
134749
134750
134751 })(this.Ext4 || (this.Ext4 = {}));
134752
134753